前言
你是否因?yàn)槎嗳蝿?wù)的依賴而頭疼?你是否被一個(gè)個(gè)嵌套的block回調(diào)弄得暈頭轉(zhuǎn)向?
快來投入Promises的懷抱吧。
正文
回調(diào)任務(wù)是很正常的現(xiàn)象,比如說購買一個(gè)商品,需要下單,然后等后臺(tái)返回。
單一任務(wù),通常只需要一個(gè)block,非常清晰;
以上面的下單為例,傳給網(wǎng)絡(luò)層一個(gè)block,購買完成之后回調(diào)即可。
但是出現(xiàn)多個(gè)任務(wù)的時(shí)候,邏輯就開始有分支,同樣以購買商品為例,在下單完成后,需要和SDK發(fā)起支付,然后根據(jù)支付結(jié)果再進(jìn)行一些提示:
任務(wù)1是下單,執(zhí)行完回調(diào)error指針(或者狀態(tài)碼)表示完成狀態(tài),同時(shí)待會(huì)下單信息,此時(shí)產(chǎn)生一個(gè)分支,成功繼續(xù)下一步,失敗執(zhí)行錯(cuò)誤block;
然后是執(zhí)行任務(wù)2購買,執(zhí)行異步的支付,根據(jù)支付結(jié)果又會(huì)產(chǎn)生一個(gè)分支。
當(dāng)連續(xù)的任務(wù)超過2個(gè)之后,分支會(huì)導(dǎo)致代碼邏輯非?;靵y。

簡(jiǎn)單畫一個(gè)流程圖來分析,上述的邏輯變得復(fù)雜的原因是因?yàn)槊恳患?jí)的block需要處理下一級(jí)block的失敗情況,導(dǎo)致邏輯分支的增多。
其實(shí)所有的失敗處理都是類似的:打日志、提示用戶,可以放在一起統(tǒng)一處理。
然后把任務(wù)一、任務(wù)二等串行執(zhí)行,流程就非常清晰。

Promises就是用來輔助實(shí)現(xiàn)這樣設(shè)計(jì)的庫。
實(shí)現(xiàn)的代碼效果如下:
- (void)workflow {
[[[[self order:@"order_id"] then:^id _Nullable(NSString * _Nullable value) {
return [self pay:value];
}] then:^id _Nullable(id _Nullable value) {
return [self check:value];
}] catch:^(NSError * _Nonnull error) {
NSLog(@"error: %@", error);
}];
}
Promises的使用
Promises庫的引入非常簡(jiǎn)單,可以使用CocoaPod,Podfile如下:
pod 'PromisesObjC'
也可以到GitHub手動(dòng)下載。
按照Promise設(shè)計(jì)模式的規(guī)范,每一個(gè)Promise應(yīng)該有三種狀態(tài):pending(等待)、fulfilled(完成)、rejected(失敗);
對(duì)應(yīng)到Promises分別是:
[FBLPromise pendingPromise]; // pending等待
[FBLPromise resolvedWith:@"anyString"]; // fulfilled完成
[FBLPromise resolvedWith:[NSError new]]; // rejected失敗
實(shí)際使用中,我們更多使用的Promises庫已經(jīng)提供好的便捷函數(shù):
啟動(dòng)一個(gè)異步任務(wù) :
[FBLPromise onQueue:dispatch_get_main_queue()
async:^(FBLPromiseFulfillBlock fulfill,
FBLPromiseRejectBlock reject) {
BOOL success = arc4random() % 2;
if (success) {
fulfill(@"success");
}
else {
reject([NSError errorWithDomain:@"learn_promises_error" code:-1 userInfo:nil]);
}
}];
或者簡(jiǎn)單使用do方法:
[FBLPromise do:^id _Nullable{
BOOL success = random() % 2;
if (success) {
return @"success";
}
else {
return [NSError errorWithDomain:@"learn_promises_error" code:-1 userInfo:nil];
}
}];
不管是async方法還是do方法,他們的返回值都是創(chuàng)建一個(gè)Promise對(duì)象,可以在Promise對(duì)象后面掛一個(gè)then方法,表示這個(gè)Promise執(zhí)行完畢之后,要繼續(xù)執(zhí)行的任務(wù):
[[[FBLPromise do:^id _Nullable{
BOOL success = arc4random() % 2;
return success ? @"do_success" : [NSError errorWithDomain:@"learn_promises_do_error" code:-1 userInfo:nil];
}] then:^id _Nullable(id _Nullable value) {
BOOL success = arc4random() % 2;
return success ? @"then_success" : [NSError errorWithDomain:@"learn_promises_then_error" code:-1 userInfo:nil];
}] catch:^(NSError * _Nonnull error) {
NSLog(@"error: %@", error);
}];
上面的catch方法表示統(tǒng)一的error處理。
promise在完成任務(wù)之后,如果滿足下面的條件會(huì)調(diào)用then的方法:
1、直接調(diào)用fulfill;
2、在do方法中返回一個(gè)值(不能為error);
3、在then方法中返回一個(gè)值;
調(diào)用reject方法或者返回一個(gè)NSError對(duì)象,都會(huì)轉(zhuǎn)到catch方法處理。
用上面的do、then、catch方法組合,就完成多個(gè)異步任務(wù)的依賴執(zhí)行:
- (void)workflow {
[[[[self order:@"order_id"] then:^id _Nullable(NSString * _Nullable value) {
return [self pay:value];
}] then:^id _Nullable(id _Nullable value) {
return [self check:value];
}] catch:^(NSError * _Nonnull error) {
NSLog(@"error: %@", error);
}];
}
- (FBLPromise<NSString *> *)order:(NSString *)orderParam {
return [FBLPromise do:^id _Nullable{
return @"order_success";
}];
}
- (FBLPromise<NSString *> *)pay:(NSString *)payParam {
return [FBLPromise do:^id _Nullable{
BOOL success = arc4random() % 2;
return success ? @"pay_success" : [NSError errorWithDomain:@"pay_error" code:-1 userInfo:nil];
}];
}
- (FBLPromise<NSString *> *)check:(NSString *)checkParam {
return [FBLPromise do:^id _Nullable{
return @"check success";
}];
}
Promises還提供了很多附加特性,以All和Any為例:
All是所有Promise都fulfill才算完成;
Any是任何一個(gè)Promise完成都會(huì)執(zhí)行fulfill;
- (void)testAllAndAny {
NSMutableArray *arr = [NSMutableArray new];
[arr addObject:[self work1]];
[arr addObject:[self work2]];
[[[FBLPromise all:arr] then:^id _Nullable(NSArray * _Nullable value) {
NSLog(@"then, value:%@", value);
return value;
}] catch:^(NSError * _Nonnull error) {
NSLog(@"all error:%@", error);
}];
[[[FBLPromise any:arr] then:^id _Nullable(NSArray * _Nullable value) {
NSLog(@"then, value:%@", value);
return value;
}] catch:^(NSError * _Nonnull error) {
NSLog(@"any error:%@", error);
}];
}
- (FBLPromise<NSString *> *)work1 {
return [FBLPromise do:^id _Nullable{
BOOL success = arc4random() % 2;
return success ? @"work1 success" : [NSError errorWithDomain:@"work1_error" code:-1 userInfo:nil];
}];
}
- (FBLPromise<NSNumber *> *)work2 {
return [FBLPromise do:^id _Nullable{
BOOL success = arc4random() % 2;
return success ? @"work2 success" : [NSError errorWithDomain:@"work2_error" code:-1 userInfo:nil];
}];
}
Promises原理解析
Promises庫的設(shè)計(jì)很簡(jiǎn)單,基于Promise設(shè)計(jì)模式和iOS的GCD來實(shí)現(xiàn)。
整個(gè)庫由Promise.m/.h和他的Catagory組成。Catagory都是附加特性,基于Promise.m/.h提供的方法做擴(kuò)展,所以這里重點(diǎn)解析下Promise.m/h。
Promise類public頭文件只有寥寥數(shù)個(gè)方法:
// 靜態(tài)方法
[FBLPromise pendingPromise]; // pending等待
[FBLPromise resolvedWith:@"anyString"]; // fulfilled完成
[FBLPromise resolvedWith:[NSError new]]; // rejected失敗
// 實(shí)例方法
- (void)fulfill:(nullable Value)value; // 完成一個(gè)promise
- (void)reject:(NSError *)error;// rejected一個(gè)promise
重點(diǎn)在于private.h提供的兩個(gè)方法:
/**
對(duì)一個(gè)promise添加fulfill和reject的回調(diào)
*/
- (void)observeOnQueue:(dispatch_queue_t)queue
fulfill:(FBLPromiseOnFulfillBlock)onFulfill
reject:(FBLPromiseOnRejectBlock)onReject NS_SWIFT_UNAVAILABLE("");
/**
創(chuàng)建一個(gè)promise,并設(shè)置fulfill、reject方法為傳進(jìn)來的block
*/
- (FBLPromise *)chainOnQueue:(dispatch_queue_t)queue
chainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfill
chainedReject:(FBLPromiseChainedRejectBlock)chainedReject NS_SWIFT_UNAVAILABLE("");
observeOnQueue方法是promise的實(shí)例方法,根據(jù)promise當(dāng)前的狀態(tài),如果是fulfilled或者rejected狀態(tài)則會(huì)dispatch_group_async到下一次執(zhí)行對(duì)應(yīng)的onFulfill和onReject回調(diào);如果是pending狀態(tài)則會(huì)創(chuàng)建_observers數(shù)組,往_observers數(shù)組中添加一個(gè)block回調(diào),當(dāng)promise執(zhí)行完畢的時(shí)候,根據(jù)state選擇onFulfill或者onReject回調(diào)。
chainOnQueue方法同樣是promise的實(shí)例方法,返回的是一個(gè)FBLPromise的對(duì)象(狀態(tài)是pending)。
方法首先創(chuàng)建的是promise對(duì)象,接著創(chuàng)建了resolver的回調(diào),然后調(diào)用observeOnQueue方法。
當(dāng)self(也是一個(gè)promise)執(zhí)行完畢后,會(huì)根據(jù)fulfill、reject回調(diào)類型接著執(zhí)行chainedFulfill、chainedReject;
最后將結(jié)果拋給resolver執(zhí)行,resolver會(huì)根據(jù)返回值value進(jìn)行判斷,如果仍是promise則遞歸執(zhí)行,否則直接調(diào)用fulfill方法。
fulfill方法則會(huì)判斷value是否為NSError,如果是NSError則轉(zhuǎn)為reject,否則將狀態(tài)改為Fulfilled,并且通知observer數(shù)組。
- (FBLPromise *)chainOnQueue:(dispatch_queue_t)queue
chainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfill
chainedReject:(FBLPromiseChainedRejectBlock)chainedReject {
NSParameterAssert(queue);
FBLPromise *promise = [[FBLPromise alloc] initPending];
__auto_type resolver = ^(id __nullable value) {
if ([value isKindOfClass:[FBLPromise class]]) {
[(FBLPromise *)value observeOnQueue:queue
fulfill:^(id __nullable value) {
[promise fulfill:value];
}
reject:^(NSError *error) {
[promise reject:error];
}];
} else {
[promise fulfill:value];
}
};
[self observeOnQueue:queue
fulfill:^(id __nullable value) {
value = chainedFulfill ? chainedFulfill(value) : value;
resolver(value);
}
reject:^(NSError *error) {
id value = chainedReject ? chainedReject(error) : error;
resolver(value);
}];
return promise;
}
Promises中的dispatch_group_enter() 和 dispatch_group_leave() 是成對(duì)使用,但是和平時(shí)使用GCD不同,這里并沒有用到dispath_group_notify方法。
在剛開始看Promises源碼時(shí),產(chǎn)生過一個(gè)疑問,為什么所有Promises的操作要放在同一個(gè)group內(nèi)?
+ (dispatch_group_t)dispatchGroup {
static dispatch_group_t gDispatchGroup;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gDispatchGroup = dispatch_group_create();
});
return gDispatchGroup;
}
直到發(fā)現(xiàn)FBLWaitForPromisesWithTimeout方法,里面有一個(gè)dispatch_group_wait方法(等待group中所有block執(zhí)行完畢,或者在指定時(shí)間結(jié)束后回調(diào))。
dispatch_group_wait方法與dispath_group_notify方法類似,只是多了一個(gè)超時(shí)時(shí)間,如果調(diào)用dispatch_group_wait(DISPATCH_TIME_FOREVER)則和dispath_group_notify方法一樣。
總結(jié)
附加的特性有很多,類似Retry、Delay等,但實(shí)際使用中Promise用do、then、catch、async等少數(shù)幾個(gè)已經(jīng)可以滿足需求。
能夠?qū)崿F(xiàn)Promise設(shè)計(jì)模式的庫比較多,Promises是性能和接口調(diào)用清晰度都比較不錯(cuò)的。
使用設(shè)計(jì)模式可以簡(jiǎn)化邏輯代碼,同時(shí)也使得代碼的健壯性更強(qiáng)。