[iOS][OC] 自定義 Promise 處理序列的非同步操作

weixin_34321977發表於2018-11-08

背景

iOS 應用中很多操作是非同步的,比如:

  • 網路請求的回撥
  • UIAlertController 等待使用者點選事件的回撥
  • 定位的回撥

等等,當這些操作需要逐個序列去執行時,由於不同條件下的回撥結果以及不同操作的回撥順序,如果連續巢狀會導致業務嚴重耦合,多個操作回撥加上可能的錯誤處理就會形成常說的“回撥地獄”(舉例如下面的虛擬碼),造成邏輯可讀性差,且影響以後的維護。

/// 回撥地獄
- (void)callbackHell {
    [TestManager doSomethingWithCompletion:^{
        NSLog(@"No 1 done");
        [TestManager doSomethingWithCompletion:^{
            NSLog(@"No 2 done");
            [TestManager doSomethingWithCompletion:^{
                NSLog(@"No 3 done");
            }];
        }];
    }];
}

針對序列情況下的非同步回撥的處理,可以將非同步回撥進行一次封裝處理,這種對一個操作執行的預期,通常稱為 Promise

將每一個非同步的回撥進行封裝後放入到 PromiseManager 處理,效果舉例如下:

/// 使用 Promise
- (void)testPromise {
    GSPromise *a = [GSPromise promiseWithHandler:^(dispatch_block_t then){
        [TestManager doSomethingWithCompletion:^{
            NSLog(@"done a"); then();
        }];
    }];
    
    GSPromise *b = [GSPromise promiseWithHandler:^(dispatch_block_t then){
        [TestManager doSomethingWithCompletion:^{
            NSLog(@"done b"); then();
        }];
    }];
    
    GSPromise *c = [GSPromise promiseWithHandler:^(dispatch_block_t then){
        [TestManager doSomethingWithCompletion:^{
            NSLog(@"done c"); then();
        }];
    }];
    
    GSPromiseManager *manager = [GSPromiseManager manangerWithPromises:@[a, b]];
    [manager addPromise:c];
}

解決方案

序列操作的順序是,一個操作結束後執行下一個操作,因此可將“執行下一個操作”這一事件,封裝入一個 block 中 (稱為 then),並要求上一個操作執行完成後呼叫。

此外,將操作需要執行的方法也封裝入一個 block 中 (稱為 handler),要求每一個操作都具備 handler 這一屬性,因此將該屬性宣告為協議 GSPromisable(如下),要求操作去遵循。

/**
 what a promise will execute

 @param then should call this block after promise done
 */
typedef void(^GSPromiseHandler)(dispatch_block_t then);

@protocol GSPromisable <NSObject>
/**
 the handler which a promise should retain
 */
@property (nonatomic, copy) GSPromiseHandler handler;

@end

其中,遵循 GSPromisable 協議的物件即 promise, 都新增到 PromiseManager 所管理的容器陣列中,每個 promise 的handler 屬性,將由外部的 PromiseManager 在合適的時機呼叫。為實現 PromiseManager 對序列的管理,manager 在執行執行操作的 handler 時將上文的 then 操作傳遞到 handler 中,如此 handler 在完成自身操作後呼叫 then,實現下一步操作的繼續,實現如下:

/// try to handle next operation
- (void)try2Start {
    if (self.isWorking) return;
    
    id <GSPromisable> first = container.firstObject;
    if (!first) return;
    
    self.working = YES;
    dispatch_block_t then = ^{
        self.working = NO;
        
        @synchronized (self) {
            if (!container.firstObject) return;
            [container removeObjectAtIndex:0];
        }
        
        [self try2Start];
    };
    
    first.handler(then);
}

值得一提的是,這種設計思路類似於前文的《[iOS] [OC] 關於block回撥、高階函式“回撥再呼叫”及專案實踐》

原始碼及demo

GSPromise@GitHub

延伸

關於非同步的操作處理有許多優秀的開源框架可以學習,推薦:

感謝同事 Zoe ,其對工作水平的不懈追求,促使更好方案的實現和改進。

加我微信溝通。


6245111-2cb11192e9612786.png

相關文章