PromiseKit,優雅的的管理多個異步操做,讓你今後遠離多層嵌套。html
用一個例子做比較:git
需求:先註冊,後登陸,提示登陸成功。github
// 常規方法:嵌套執行多個任務
[self signUpWithUserName:uid pwd:pwd resultBlock:^(BOOL success, NSString *message) {
if (success) {
[self signInWithUserName:uid pwd:pwd resultBlock:^(BOOL success, NSString *message) {
if (success) {
NSLog(@"登陸成功");
}
}];
}
}];
複製代碼
// PromiseKit提供的方法,簡潔清晰的展現多任務操做
signUpPromise.then(^{
return signInPromise;
}).then(^{
NSLog(@"登陸成功");
}).catch(^(NSError* error){
});
複製代碼
因而可知,用PromiseKit實現的方法更美觀,且更易維護。算法
仍是上面那個需求,下面是具體實現步驟:數組
1. 修改現有的註冊任務和登陸任務,以下:
// 註冊僞代碼(登陸代碼相似)
- (AnyPromise*)promise_signUpWithUserName:(NSString*)uid pwd:(NSString*)pwd {
return [AnyPromise promiseWithAdapterBlock:^(PMKAdapter _Nonnull adapter) {
// 網絡請求 xxx
if(註冊成功) {
adapter(@"註冊成功", nil);
}else {
adapter(nil,[self errorWithMessage:"註冊失敗!"]); // error本身定義
}
}];
}
- (NSError*)errorWithMessage:(NSString*)msg {
return [[NSError alloc] initWithDomain:NSNetServicesErrorDomain code:-101 userInfo:@{NSLocalizedDescriptionKey:msg}];
}
2. 在一個地方管理全部異步操做
[HTTPUtil promise_signUpWithUserName:uid pwd:pwd].then(^{// 註冊成功走這裏
NSLog(@"註冊成功");
return [HTTPUtil promise_signInWithUserName:uid pwd:pwd];// 接下來執行登陸操做
}).then(^(NSDate* date){// 登陸成功走這裏
NSLog(@"登陸成功, 時間:%@",date);
}).catch(^(NSError* error){// 註冊失敗或者登陸失敗跳到這裏,不會執行then方法
NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey);
});
複製代碼
接下來逐句解讀該段代碼,並穿插知識點。xcode
AnyPromise
能夠看做是任務
,下文以任務
代替。promise
- (AnyPromise*)promise_signUpWithUserName:(NSString*)uid pwd:(NSString*)pwd {
return [AnyPromise promiseWithAdapterBlock:^(PMKAdapter _Nonnull adapter) {
}];
}
複製代碼
任務
的全部初始化方法以下,可在源碼查看它們說明,這裏不一一解釋:安全
// 經常使用
+ (instancetype __nonnull)promiseWithAdapterBlock:(void (^ __nonnull)(PMKAdapter __nonnull adapter))block;
+ (instancetype __nonnull)promiseWithResolverBlock:(void (^ __nonnull)(__nonnull PMKResolver))resolveBlock;
+ (instancetype __nonnull)promiseWithValue:(__nullable id)value;
+ (instancetype __nonnull)promiseWithIntegerAdapterBlock:(void (^ __nonnull)(PMKIntegerAdapter __nonnull adapter))block;
+ (instancetype __nonnull)promiseWithBooleanAdapterBlock:(void (^ __nonnull)(PMKBooleanAdapter __nonnull adapter))block;
- (instancetype __nonnull)initWithResolver:(PMKResolver __strong __nonnull * __nonnull)resolver ;
複製代碼
任務
的初始化方法中附有一個Block:PMKAdapter
,其返回的參數表達了該任務
的處理結果是成功仍是失敗,如:bash
if(登陸成功) {
adapter([NSDate date], nil);// 處理成功
}else {
adapter(nil, error); // 處理失敗
}
複製代碼
任務
有2種狀態:pending
和resolved
(本人習慣翻譯爲:等待處理和處理結束), 其中resolved
包括fulfilled
(處理成功)和rejected
(處理失敗)。網絡
爲何上段代碼中adapter([NSDate date], nil);
表明處理成功,而adapter(nil, error);
表明處理失敗呢?
接下來了解PMKAdapter
:
PMKAdapter
能夠傳遞2個參數,第一個是id類型的任何對象,能夠是nil,第二個是NSError
對象,也能夠是nil。傳遞不一樣參數,任務
的狀態會不一樣,以下圖所示:
所以能夠總結PMKAdapter
的參數:
總結一:若是任意一個參數的類型是
NSError
,任務
處理失敗
;
總結二:2個參數都是nil或者第一個是id,第二個是nil,
任務
處理成功
。
處理結果將影響第三步的操做。
下面是全部Block的定義:
typedef void (^PMKAdapter)(id __nullable, NSError * __nullable) ;
typedef void (^PMKResolver)(id __nullable);
typedef void (^PMKIntegerAdapter)(NSInteger, NSError * __nullable) ;
typedef void (^PMKBooleanAdapter)(BOOL, NSError * __nullable) ;
複製代碼
signUpPromise.then(^{// 註冊成功走這裏
NSLog(@"註冊成功");
return signInPromise;// 返回signInPromise:接下來執行登陸操做
}).then(^(NSDate* date){// 登陸成功走這裏
NSLog(@"登陸成功, 時間:%@",date);
}).catch(^(NSError* error){// 註冊失敗或者登陸失敗跳到這裏
NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey);
});
複製代碼
若是
任務
處理成功
,就會執行緊接其後的then
方法;若是處理失敗
,則會跳事後面的全部then
方法,執行catch
方法。
第一:若是任務
處理成功,
能夠在then
方法裏面手動
增長最多3個參數:
.then(^(NSDate* date, ..., ...){
NSLog(@"date: %@",date);
})
複製代碼
第二:若是任務
處理失敗
,能夠把參數傳給NSError
:
adapter(nil,[self errorWithMessage:"登陸失敗!"]);
複製代碼
而後在catch
方法提取:
.catch(^(NSError* error){
NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey);
})
複製代碼
AnyPromise是PromiseKit的關鍵類,一個AnyPromise對象能夠看做是一個任務。
@property (nonatomic, readonly) __nullable id value; // 已經處理的Promise的值
@property (nonatomic, readonly) BOOL pending; // 等待處理
@property (nonatomic, readonly) BOOL fulfilled; // 處理成功
@property (nonatomic, readonly) BOOL rejected; // 處理失敗
複製代碼
then
:當任務
處理成功
,在主線程執行。
- (AnyPromise * __nonnull (^ __nonnull)(id __nonnull))then;
複製代碼
thenInBackground
:當任務
處理成功
,在默認線程執行。
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))thenInBackground;
複製代碼
thenOn
:當任務
處理成功
,在指定線程執行。
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, id __nonnull))thenOn;
複製代碼
catch
:當任務
處理失敗
,在主線程運行。
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catch;
複製代碼
catchInBackground
:當任務
處理失敗
,在global線程
執行。
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catchInBackground;
複製代碼
catchOn
:當任務
處理失敗
,在指定線程執行。
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, id __nonnull))catchOn;
複製代碼
ensure
:當任務
處理結束
,在主線程執行。
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))ensure;
複製代碼
ensureOn
:當任務
處理結束
,在指定線程執行。
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, dispatch_block_t __nonnull))ensureOn;
複製代碼
全部鏈接詞在指定線程執行的用法相似:
.ensureOn(queue, ^{
})
複製代碼
AnyPromise *__nonnull PMKJoin(NSArray * __nonnull promises);
複製代碼
等待全部任務
處理結束
,執行下一步。(全部任務
處理結束
纔會處理處理失敗
,若是有就跳轉到then
方法)
官方註釋:
PMKJoin
waits on all provided promises, then rejects if any of those promises rejects, otherwise it fulfills with values from the provided promises.
大意:
PMKJoin
將會等待全部任務
(promises
參數提供)處理結束
,以後若是有一個任務
處理失敗
,PMKJoin
纔會處理失敗
,不然處理成功
。
例子:吃完飯,喝完湯,才能吃水果。
PMKJoin(@[ricePromise, soupPromise]).then(^(NSArray* messages){// 此時參數是數組類型
return fruitPromise;
}).catch(^(NSError* error){
NSArray* promises = error.userInfo[PMKJoinPromisesKey];// 錯誤信息中包含了全部任務
for (AnyPromise* promise in promises) {
if (promise.rejected) {
NSLog(@"%@ is rejected",promise);
}
}
});
複製代碼
extern AnyPromise * __nonnull PMKWhen(id __nonnull input);
複製代碼
等待全部任務
處理成功
,執行下一步。(一旦有任務
處理失敗
,當即跳轉到thencatch
方法,且中止執行其餘任務)
官方註釋:
PMKWhen
rejects as soon as one of the provided promises rejects.
大意:只要其中一個
任務
(input
參數提供)處理失敗
,PMKWhen
當即處理失敗
。
例子:作飯,拿碗筷,才能吃飯。
PMKWhen(@[cookPromise, setPromise]).then(^(NSArray* messages){// 此時參數是數組類型
return eatPromise;
}).catch(^(NSError* error) {// "處理失敗"的任務
NSInteger index = [error.userInfo[PMKFailingPromiseIndexKey] integerValue];
NSLog(@"index:%@ error: %@\n", @(index),error.userInfo[NSLocalizedDescriptionKey]);
});
複製代碼
extern AnyPromise * __nonnull PMKRace(NSArray * __nonnull promises);
複製代碼
只要處理結束
一個任務
,當即執行then
或者catch
方法,其餘任務
繼續執行。(只關心第一個處理結束
的任務
,不關心其餘任務
)
例子:在衆多算法中找出解題速度最快的那個。
PMKRace(@[algPromise1, algPromise2, algPromise3]).then(^(id message){
NSLog(@"%@",message);
}).catch(^(NSError* error){
NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey]);
});
複製代碼
extern AnyPromise * __nonnull PMKAfter(NSTimeInterval duration);
複製代碼
延遲必定時間執行任務
,至關於dispatch_after
。
例子:註冊成功後3秒再登陸。
signUpPromise.then(^{
// 註冊成功
return PMKAfter(3.f).then(^{// 延遲執行signInPromise
return signInPromise;
});
}).then(^{
// 登陸成功
}).catch(^(NSError* error){
// handle error
});
複製代碼
extern id __nullable PMKHang(AnyPromise * __nonnull promise);
複製代碼
阻塞線程,直到任務
處理結束
。這個作法不安全,只在調試時用!!!
例子:阻塞當前線程,直到註冊成功或者註冊失敗纔會執行next step。
id value = PMKHang([self promise_signUpWithUserName:uid pwd:pwd]);
NSLog(@"%@ is resolved",value);
// next step
// ...
複製代碼
設定任務執行的超時時間
// 限定註冊任務的超時時間爲2秒
PMKRace(@[PMKWhen([HTTPUtil promise_signUpWithUserName:uid pwd:pwd]), PMKAfter(2.).then(^{
NSLog(@"計時結束~");
})]).then(^(NSString* msg){
NSLog(@"%@",msg);
});
複製代碼
剛着手把PromiseKit集成到項目的時候,按照網上的教程,不管是CocoaPods仍是手動安裝都失敗,通過摸索,步驟以下:
版本:Xcode 10.1,PromiseKit 6.8.4
把PromiseKit項目放在你的項目文件夾,如圖:
#import <PromiseKit/PromiseKit.h>
複製代碼
此時可能報錯:
解決方法:把Build Active Architecture Only屬性改成YES
這樣,就能在模擬器上使用PromiseKit。可是依然不能在真機上運行,可能報錯:
解決方法:修改PromiseKit的可用架構
到此,成功集成PromiseKit。
參考: