iOS的異步處理神器——Promises

前言

你是否因?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)。

附錄

Promises
PromiseKit

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容