前言
你是否因為多任務(wù)的依賴而頭疼墩剖?你是否被一個個嵌套的block回調(diào)弄得暈頭轉(zhuǎn)向?
快來投入Promises的懷抱吧痒筒。
正文
回調(diào)任務(wù)是很正常的現(xiàn)象宰闰,比如說購買一個商品,需要下單簿透,然后等后臺返回移袍。
單一任務(wù),通常只需要一個block老充,非常清晰葡盗;
以上面的下單為例,傳給網(wǎng)絡(luò)層一個block啡浊,購買完成之后回調(diào)即可觅够。
但是出現(xiàn)多個任務(wù)的時候,邏輯就開始有分支巷嚣,同樣以購買商品為例喘先,在下單完成后,需要和SDK發(fā)起支付廷粒,然后根據(jù)支付結(jié)果再進行一些提示:
任務(wù)1是下單窘拯,執(zhí)行完回調(diào)error指針(或者狀態(tài)碼)表示完成狀態(tài),同時待會下單信息坝茎,此時產(chǎn)生一個分支涤姊,成功繼續(xù)下一步,失敗執(zhí)行錯誤block嗤放;
然后是執(zhí)行任務(wù)2購買砂轻,執(zhí)行異步的支付,根據(jù)支付結(jié)果又會產(chǎn)生一個分支斤吐。
當(dāng)連續(xù)的任務(wù)超過2個之后搔涝,分支會導(dǎo)致代碼邏輯非常混亂和措。
簡單畫一個流程圖來分析庄呈,上述的邏輯變得復(fù)雜的原因是因為每一級的block需要處理下一級block的失敗情況,導(dǎo)致邏輯分支的增多派阱。
其實所有的失敗處理都是類似的:打日志诬留、提示用戶,可以放在一起統(tǒng)一處理。
然后把任務(wù)一文兑、任務(wù)二等串行執(zhí)行盒刚,流程就非常清晰。
Promises就是用來輔助實現(xiàn)這樣設(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庫的引入非常簡單因块,可以使用CocoaPod,Podfile如下:
pod 'PromisesObjC'
也可以到GitHub手動下載籍铁。
按照Promise設(shè)計模式的規(guī)范涡上,每一個Promise應(yīng)該有三種狀態(tài):pending(等待)、fulfilled(完成)拒名、rejected(失敗)吩愧;
對應(yīng)到Promises分別是:
[FBLPromise pendingPromise]; // pending等待
[FBLPromise resolvedWith:@"anyString"]; // fulfilled完成
[FBLPromise resolvedWith:[NSError new]]; // rejected失敗
實際使用中,我們更多使用的Promises庫已經(jīng)提供好的便捷函數(shù):
啟動一個異步任務(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]);
}
}];
或者簡單使用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)建一個Promise對象雁佳,可以在Promise對象后面掛一個then方法,表示這個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ù)之后,如果滿足下面的條件會調(diào)用then的方法:
1梢杭、直接調(diào)用fulfill温兼;
2感混、在do方法中返回一個值(不能為error)比搭;
3、在then方法中返回一個值竖般;
調(diào)用reject方法或者返回一個NSError對象咒唆,都會轉(zhuǎn)到catch方法處理届垫。
用上面的do、then全释、catch方法組合装处,就完成多個異步任務(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是任何一個Promise完成都會執(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è)計很簡單,基于Promise設(shè)計模式和iOS的GCD來實現(xiàn)李命。
整個庫由Promise.m/.h和他的Catagory組成登淘。Catagory都是附加特性,基于Promise.m/.h提供的方法做擴展封字,所以這里重點解析下Promise.m/h黔州。
Promise類public頭文件只有寥寥數(shù)個方法:
// 靜態(tài)方法
[FBLPromise pendingPromise]; // pending等待
[FBLPromise resolvedWith:@"anyString"]; // fulfilled完成
[FBLPromise resolvedWith:[NSError new]]; // rejected失敗
// 實例方法
- (void)fulfill:(nullable Value)value; // 完成一個promise
- (void)reject:(NSError *)error;// rejected一個promise
重點在于private.h提供的兩個方法:
/**
對一個promise添加fulfill和reject的回調(diào)
*/
- (void)observeOnQueue:(dispatch_queue_t)queue
fulfill:(FBLPromiseOnFulfillBlock)onFulfill
reject:(FBLPromiseOnRejectBlock)onReject NS_SWIFT_UNAVAILABLE("");
/**
創(chuàng)建一個promise耍鬓,并設(shè)置fulfill、reject方法為傳進來的block
*/
- (FBLPromise *)chainOnQueue:(dispatch_queue_t)queue
chainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfill
chainedReject:(FBLPromiseChainedRejectBlock)chainedReject NS_SWIFT_UNAVAILABLE("");
observeOnQueue方法是promise的實例方法流妻,根據(jù)promise當(dāng)前的狀態(tài)牲蜀,如果是fulfilled或者rejected狀態(tài)則會dispatch_group_async到下一次執(zhí)行對應(yīng)的onFulfill和onReject回調(diào);如果是pending狀態(tài)則會創(chuàng)建_observers數(shù)組绅这,往_observers數(shù)組中添加一個block回調(diào)涣达,當(dāng)promise執(zhí)行完畢的時候,根據(jù)state選擇onFulfill或者onReject回調(diào)君躺。
chainOnQueue方法同樣是promise的實例方法峭判,返回的是一個FBLPromise的對象(狀態(tài)是pending)开缎。
方法首先創(chuàng)建的是promise對象棕叫,接著創(chuàng)建了resolver的回調(diào),然后調(diào)用observeOnQueue方法奕删。
當(dāng)self(也是一個promise)執(zhí)行完畢后俺泣,會根據(jù)fulfill、reject回調(diào)類型接著執(zhí)行chainedFulfill完残、chainedReject伏钠;
最后將結(jié)果拋給resolver執(zhí)行,resolver會根據(jù)返回值value進行判斷谨设,如果仍是promise則遞歸執(zhí)行熟掂,否則直接調(diào)用fulfill方法。
fulfill方法則會判斷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() 是成對使用誉券,但是和平時使用GCD不同,這里并沒有用到dispath_group_notify方法刊愚。
在剛開始看Promises源碼時踊跟,產(chǎn)生過一個疑問,為什么所有Promises的操作要放在同一個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方法商玫,里面有一個dispatch_group_wait方法(等待group中所有block執(zhí)行完畢,或者在指定時間結(jié)束后回調(diào))牡借。
dispatch_group_wait方法與dispath_group_notify方法類似决帖,只是多了一個超時時間,如果調(diào)用dispatch_group_wait(DISPATCH_TIME_FOREVER)則和dispath_group_notify方法一樣蓖捶。
總結(jié)
附加的特性有很多地回,類似Retry、Delay等,但實際使用中Promise用do刻像、then畅买、catch、async等少數(shù)幾個已經(jīng)可以滿足需求细睡。
能夠?qū)崿F(xiàn)Promise設(shè)計模式的庫比較多谷羞,Promises是性能和接口調(diào)用清晰度都比較不錯的。
使用設(shè)計模式可以簡化邏輯代碼溜徙,同時也使得代碼的健壯性更強湃缎。