前言
在iOS中我們一般使用delegate(代理)或者block(閉包)來進行非同步操作,當要執行多個非同步操作,必須將第二個巢狀在第一個的完成內,並且還要正常處理錯誤。這使得程式碼結構異常的混亂,不方便檢視。
相信碼過JS
的同學都清楚,在es6中新增promise
的語法,從此非同步操作更加的靈活,它使得回撥與事件進行了組合,而且寫法優美。谷歌在18年上半年開源了promises庫,使iOS上的非同步程式設計更加便捷,其使用方式與JS中的promise
一致,並支援OC與Swift。
介紹
Promise
代表非同步執行的結果或者錯誤資訊。其擁有以下三種狀態
- pending 正在處理
- fulfilled 處理完成得到的結果
- rejected 發生錯誤的資訊
當狀態發生變更後,就無法再次變更其狀態。同時promise
不限制其訂閱者,當狀態發生變化後,所有的訂閱者都能接收到通知。同時訂閱者可以返回另一個promise
物件,從而形成管道通訊。
管道通訊過程可以自由的使用執行緒
原理分析
從上圖中可以得知目前支援的操作,我們可以在FBLPromise
檔案查詢Promise
的實現原理
宣告狀態、閉包
//三種狀態型別
typedef NS_ENUM(NSInteger, FBLPromiseState) {
FBLPromiseStatePending = 0,
FBLPromiseStateFulfilled,
FBLPromiseStateRejected,
};
//閉包的回撥型別
typedef void (^FBLPromiseObserver)(FBLPromiseState state, id __nullable resolution);```
複製程式碼
相關的快取
@implementation FBLPromise {
//當前promise的狀態
FBLPromiseState _state;
//存放執行相關邏輯的結果儲存
id __nullable _value;
//存放執行相關邏輯的錯誤
NSError *__nullable _error;
//存放訂閱者的資訊
NSMutableArray<FBLPromiseObserver> *_observers;
}
複製程式碼
完成或失敗的操作
完成或失敗的操作其實是遍歷所有訂閱者,將當前的狀態和執行後的結果通知訂閱者。執行完畢後改變快取的狀態值,就算後期再次呼叫也不會響應。達到只要狀態變更就無法再次啟動的效果。
- (void)fulfill:(nullable id)value {
if ([value isKindOfClass:[NSError class]]) {
[self reject:(NSError *)value];
} else {
@synchronized(self) {
if (_state == FBLPromiseStatePending) {
//變更狀態
_state = FBLPromiseStateFulfilled;
_value = value;
_pendingObjects = nil;
//通知訂閱者所訂閱的資訊
for (FBLPromiseObserver observer in _observers) {
observer(_state, _value);
}
_observers = nil;
dispatch_group_leave(FBLPromise.dispatchGroup);
}
}
}
}
複製程式碼
訂閱者
訂閱相關資訊時,先去判斷當前的狀態型別,當此時已經執行完畢後,會馬上回撥相關的執行結果。
- (void)observeOnQueue:(dispatch_queue_t)queue
fulfill:(FBLPromiseOnFulfillBlock)onFulfill
reject:(FBLPromiseOnRejectBlock)onReject {
@synchronized(self) {
//先判斷當前的狀態,只有正在執行的過程中才可以訂閱
switch (_state) {
case FBLPromiseStatePending: {
if (!_observers) {
_observers = [[NSMutableArray alloc] init];
}
//增加一個閉包,採用閉包的方式儲存外部變數環境
[_observers addObject:^(FBLPromiseState state, id __nullable resolution) {
dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{
switch (state) {
case FBLPromiseStatePending:
break;
case FBLPromiseStateFulfilled:
onFulfill(resolution);
break;
case FBLPromiseStateRejected:
onReject(resolution);
break;
}
});
}];
break;
}
case FBLPromiseStateFulfilled: {
dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{
onFulfill(self->_value);
});
break;
}
case FBLPromiseStateRejected: {
dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{
onReject(self->_error);
});
break;
}
}
}
}
複製程式碼
此處是整個promise操作的重中之重,也是我們promise中使用最多的原理所在,從FBLPromise+Then
中可以看出該方法是then
的原理,也是實現管道通訊的原理
- (FBLPromise *)chainOnQueue:(dispatch_queue_t)queue
chainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfill
chainedReject:(FBLPromiseChainedRejectBlock)chainedReject {
//建立新的promise
FBLPromise *promise = [[FBLPromise alloc] initPending];
//定義執行的閉包,該閉包是管道通訊的精華,也是為什麼下一個then中能收到當前then中返回promise執行結果的關鍵
__auto_type resolver = ^(id __nullable value) {
//判斷then返回的是否是promise
if ([value isKindOfClass:[FBLPromise class]]) {
//訂閱then返回的promise,主要是為了呼叫下一個then
[(FBLPromise *)value observeOnQueue:queue
fulfill:^(id __nullable value) {
[promise fulfill:value];
}
reject:^(NSError *error) {
[promise reject:error];
}];
} else {
//若返回的不是promise中,則馬上傳給下一個then
[promise fulfill:value];
}
};
//訂閱當前的promise
[self observeOnQueue:queue
fulfill:^(id __nullable value) {
//執行then操作後的返回值,可能是promise也可能是其他值
value = chainedFulfill ? chainedFulfill(value) : value;
//呼叫上面建立的閉包
resolver(value);
}
reject:^(NSError *error) {
id value = chainedReject ? chainedReject(error) : error;
resolver(value);
}];
return promise;
}
複製程式碼
使用方法
此處僅展示swift的語法,oc上的使用請參考FBL
字首的類,或者直接閱讀官方文件
建立方式
預設是在主執行緒中使用,如需在其他執行緒中使用,可以設定on: DispatchQueue
,此處不作介紹
非同步方式
let promise = Promise<Int> { fulfill, reject in
let vail = true
if vail {
fulfil(12)
}else {
reject(error)
}
}
複製程式碼
同步方式
//採用預先定義方式
let promise = Promise<Int>.pending()
...
if success {
promise.fulfill(12)
} else {
promise.reject(error)
}
//直接發起結果的方式
let promise = Promise(12)
let promise = Promise(error)
複製程式碼
then使用
let numberPromise = Promise { fulfill, _ in
fulfill(42)
}
let stringPromise = numberPromise.then { number in
return Promise { fulfill, _ in
fulfill(String(number))
}
}
typealias Block = (Int) -> [Int]
let blockPromise = stringPromise.then { value in
return Promise<Block> { fulfill, _ in
fulfill({ number in
return [number + (Int(value) ?? 0)]
})
}
}
let finalPromise = blockPromise.then { (value: @escaping Block) -> Int? in
return value(42).first
}
let postFinalPromise = finalPromise.then { number in
return number ?? 0
}
複製程式碼
Catch
catch是處理promise中出現錯誤的情況,在promise管道中,當其中一個promise執行出現錯誤時,會忽略這個之後的管道直接將錯誤傳輸到catch中處理。
let promise = Promise<AnyObject> {
return error
}.catch { error in
//處理錯誤
}
複製程式碼
操作符
All
只有當all中所有的promise否成功執行後才回撥,回撥引數未all中所有執行結果的元組,當其中一個呼叫rejected
時,都直接回撥錯誤
let promise1 = Promise<Int?> { fulfill, _ in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 1, execute: {
fulfill(42)
})
}
let promise2 = Promise<Int?> { fulfill, _ in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
fulfill(13)
})
}
let promise3 = Promise<Int?> { fulfill, _ in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
fulfill(nil)
})
}
let combinedPromise = all([promise1, promise2, promise3]).then { value in
print(value) //[Optional(42), Optional(13), nil]
}
複製程式碼
Always
無論管道中是順利成功執行還是出現錯誤,最終都會執行always的閉包
var count = 0
let promise = Promise<Void> { _, reject in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
reject(DYError.promise)
})
}.always {
count += 1
}.catch { error in
count += 1
}.always {
print(count)
}
複製程式碼
Any
與all
操作型別,但是Any
中即便有其中的出現了錯誤也會返回元組資料嗎,其元組型別是Maybe
,包含error
與value
相關資訊
let promise1 = Promise<Int?> { fulfill, _ in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
fulfill(42)
})
}
let promise2 = Promise<Int?> { fulfill, _ in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
fulfill(13)
})
}
let promise3 = Promise<Int?> { _, reject in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 1, execute: {
reject(DYError.promise)
})
}
let combinedPromise = any([promise1, promise2, promise3]).then { value in
let item = value.first
print(value.first?.value)
}
複製程式碼
Await
使用該操作,可以同步等待在不同執行緒上執行的promise。該語法與ES8中async/await
的使用時類似的
Promise<Int> {
let minusFive = try await(calculator.negate(5))
let twentyFive = try await(calculator.multiply(minusFive, minusFive))
let twenty = try await(calculator.add(twentyFive, minusFive))
let five = try await(calculator.subtract(twentyFive, twenty))
let zero = try await(calculator.add(minusFive, five))
return try await(calculator.multiply(zero, five))
}.then { result in
}.catch { error in
}
複製程式碼
Delay
該操作返回一個預先定義的promise,等到給定的時間後執行,或者立即執行錯誤
let promise = Promise(42).delay(2)
promise.catch { err in
print(err)
}.then { value in
print(value)
}
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 1, execute: {
promise.reject(DYError.promise)
})
複製程式碼
Race
該操作與all
相似,但是返回的是先執行完成的promise結果或者錯誤
let promise1 = Promise<Any?> { fulfill, _ in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
fulfill(42)
})
}
let promise2 = Promise<Any?> { fulfill, _ in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
fulfill("hello world")
})
}
let promise3 = Promise<Any?> { fulfill, _ in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
fulfill([44])
})
}
let promise4 = Promise<Any?> { fulfill, _ in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
fulfill(nil)
})
}
race([promise1,promise2,promise3,promise4]).then { (value) in
print(value)
}
複製程式碼
Recover
與Catch
效果相同,但是該操作符不會隔斷管道中其他promise的執行
let promise = Promise<Int> { _, reject in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
reject(DYError.promise)
})
}.recover { error -> Promise<Int> in
print(error)
return Promise { fulfill, _ in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
fulfill(1)
})
}
}.catch { err in
print(err)
}.then { value in
print(value)
}
複製程式碼
Reduce
結合其他資料進行變換
let numbers = [1, 2, 3]
Promise("0").reduce(numbers) { partialString, nextNumber in
Promise(partialString + ", " + String(nextNumber))
}.then { string in
// Final result = 0, 1, 2, 3
print("Final result = \(string)")
}
複製程式碼
Retry
promise執行失敗時,嘗試重新執行。在未設定時間的前提下,預設延遲1s重新執行
var count = 0
retry(attempts: 3, delay: 5, condition: { (num, error) -> Bool in
print("\(num)" + error.localizedDescription)
if num == 2 {
//馬上執行錯誤
return false
}
return true
}) { () -> Promise<Int> in
return count == 3 ? Promise(42) : Promise(DYError.promise)
}.then { (value) in
print(value)
}.catch { (error) in
print(error)
}
複製程式碼
Timeout
在指定的時間內如果沒有執行完成或者傳送錯誤,則自發錯誤
let promise = Promise { fulfill, _ in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
fulfill(42)
})
}.timeout(1).catch { err in
print(err) //timedOut
}.then { value in
print(value)
}
複製程式碼
Validate
判斷是否有效,如果無效則自發錯誤
let promise = Promise { fulfill, _ in
DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
fulfill(42)
})
}.validate { value in
return value == 1
}.catch { err in
print(err) //validationFailure
}.then { value in
print(value)
}
複製程式碼