iOS中的Promise

孤盞茗發表於2019-04-17

前言

在iOS中我們一般使用delegate(代理)或者block(閉包)來進行非同步操作,當要執行多個非同步操作,必須將第二個巢狀在第一個的完成內,並且還要正常處理錯誤。這使得程式碼結構異常的混亂,不方便檢視。

相信碼過JS的同學都清楚,在es6中新增promise的語法,從此非同步操作更加的靈活,它使得回撥與事件進行了組合,而且寫法優美。谷歌在18年上半年開源了promises庫,使iOS上的非同步程式設計更加便捷,其使用方式與JS中的promise一致,並支援OC與Swift。

介紹

Promise代表非同步執行的結果或者錯誤資訊。其擁有以下三種狀態

  • pending 正在處理
  • fulfilled 處理完成得到的結果
  • rejected 發生錯誤的資訊

當狀態發生變更後,就無法再次變更其狀態。同時promise不限制其訂閱者,當狀態發生變化後,所有的訂閱者都能接收到通知。同時訂閱者可以返回另一個promise物件,從而形成管道通訊。

管道通訊過程可以自由的使用執行緒

原理分析

iOS中的Promise

從上圖中可以得知目前支援的操作,我們可以在FBLPromise檔案查詢Promise的實現原理

宣告狀態、閉包

//三種狀態型別
typedef NS_ENUM(NSInteger, FBLPromiseState) {
  FBLPromiseStatePending = 0,
  FBLPromiseStateFulfilled,
  FBLPromiseStateRejected,
};
//閉包的回撥型別
typedef void (^FBLPromiseObserver)(FBLPromiseState state, id __nullable resolution);```
複製程式碼

相關的快取

@implementation FBLPromise {
  //當前promise的狀態
  FBLPromiseState _state;
  //存放執行相關邏輯的結果儲存
  id __nullable _value;
  //存放執行相關邏輯的錯誤
  NSError *__nullable _error;
  //存放訂閱者的資訊
  NSMutableArray<FBLPromiseObserver> *_observers;
}
複製程式碼

完成或失敗的操作

完成或失敗的操作其實是遍歷所有訂閱者,將當前的狀態和執行後的結果通知訂閱者。執行完畢後改變快取的狀態值,就算後期再次呼叫也不會響應。達到只要狀態變更就無法再次啟動的效果。

- (void)fulfill:(nullable id)value {
  if ([value isKindOfClass:[NSError class]]) {
    [self reject:(NSError *)value];
  } else {
    @synchronized(self) {
      if (_state == FBLPromiseStatePending) {
        //變更狀態
        _state = FBLPromiseStateFulfilled;
        _value = value;
        _pendingObjects = nil;
        //通知訂閱者所訂閱的資訊
        for (FBLPromiseObserver observer in _observers) {
          observer(_state, _value);
        }
        _observers = nil;
        dispatch_group_leave(FBLPromise.dispatchGroup);
      }
    }
  }
}
複製程式碼

訂閱者

訂閱相關資訊時,先去判斷當前的狀態型別,當此時已經執行完畢後,會馬上回撥相關的執行結果。

- (void)observeOnQueue:(dispatch_queue_t)queue
               fulfill:(FBLPromiseOnFulfillBlock)onFulfill
                reject:(FBLPromiseOnRejectBlock)onReject {
  @synchronized(self) {
    //先判斷當前的狀態,只有正在執行的過程中才可以訂閱
    switch (_state) {
      case FBLPromiseStatePending: {
        if (!_observers) {
          _observers = [[NSMutableArray alloc] init];
        }
        //增加一個閉包,採用閉包的方式儲存外部變數環境
        [_observers addObject:^(FBLPromiseState state, id __nullable resolution) {
          dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{
            switch (state) {
              case FBLPromiseStatePending:
                break;
              case FBLPromiseStateFulfilled:
                onFulfill(resolution);
                break;
              case FBLPromiseStateRejected:
                onReject(resolution);
                break;
            }
          });
        }];
        break;
      }
      case FBLPromiseStateFulfilled: {
        dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{
          onFulfill(self->_value);
        });
        break;
      }
      case FBLPromiseStateRejected: {
        dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{
          onReject(self->_error);
        });
        break;
      }
    }
  }
}
複製程式碼

此處是整個promise操作的重中之重,也是我們promise中使用最多的原理所在,從FBLPromise+Then中可以看出該方法是then的原理,也是實現管道通訊的原理

- (FBLPromise *)chainOnQueue:(dispatch_queue_t)queue
              chainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfill
               chainedReject:(FBLPromiseChainedRejectBlock)chainedReject {

  //建立新的promise
  FBLPromise *promise = [[FBLPromise alloc] initPending];
  //定義執行的閉包,該閉包是管道通訊的精華,也是為什麼下一個then中能收到當前then中返回promise執行結果的關鍵
  __auto_type resolver = ^(id __nullable value) {
    //判斷then返回的是否是promise
    if ([value isKindOfClass:[FBLPromise class]]) {
      //訂閱then返回的promise,主要是為了呼叫下一個then
      [(FBLPromise *)value observeOnQueue:queue
          fulfill:^(id __nullable value) {
            [promise fulfill:value];
          }
          reject:^(NSError *error) {
            [promise reject:error];
          }];
    } else {
      //若返回的不是promise中,則馬上傳給下一個then
      [promise fulfill:value];
    }
  };
  //訂閱當前的promise
  [self observeOnQueue:queue
      fulfill:^(id __nullable value) {
        //執行then操作後的返回值,可能是promise也可能是其他值
        value = chainedFulfill ? chainedFulfill(value) : value;
        //呼叫上面建立的閉包
        resolver(value);
      }
      reject:^(NSError *error) {
        id value = chainedReject ? chainedReject(error) : error;
        resolver(value);
      }];
  return promise;
}
複製程式碼

使用方法

此處僅展示swift的語法,oc上的使用請參考FBL字首的類,或者直接閱讀官方文件

建立方式

預設是在主執行緒中使用,如需在其他執行緒中使用,可以設定on: DispatchQueue,此處不作介紹

非同步方式

let promise = Promise<Int> { fulfill, reject in
    let vail = true
    if vail {
        fulfil(12)
    }else {
        reject(error)
    }
}
複製程式碼

同步方式

//採用預先定義方式
let promise = Promise<Int>.pending()
...
if success {
  promise.fulfill(12)
} else {
  promise.reject(error)
}

//直接發起結果的方式
let promise = Promise(12)
let promise = Promise(error)

複製程式碼

then使用

let numberPromise = Promise { fulfill, _ in
    fulfill(42)
}
let stringPromise = numberPromise.then { number in
    return Promise { fulfill, _ in
        fulfill(String(number))
    }
}
typealias Block = (Int) -> [Int]
let blockPromise = stringPromise.then { value in
    return Promise<Block> { fulfill, _ in
        fulfill({ number in
            return [number + (Int(value) ?? 0)]
        })
    }
}
let finalPromise = blockPromise.then { (value: @escaping Block) -> Int? in
    return value(42).first
}
let postFinalPromise = finalPromise.then { number in
    return number ?? 0
}
複製程式碼

Catch

catch是處理promise中出現錯誤的情況,在promise管道中,當其中一個promise執行出現錯誤時,會忽略這個之後的管道直接將錯誤傳輸到catch中處理。

let promise = Promise<AnyObject> {
    return error
}.catch { error in
    //處理錯誤
}
複製程式碼

操作符

All

只有當all中所有的promise否成功執行後才回撥,回撥引數未all中所有執行結果的元組,當其中一個呼叫rejected時,都直接回撥錯誤

let promise1 = Promise<Int?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 1, execute: {
        fulfill(42)
    })
}
let promise2 = Promise<Int?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
        fulfill(13)
     })
}
let promise3 = Promise<Int?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
        fulfill(nil)
    })
}
        
let combinedPromise = all([promise1, promise2, promise3]).then { value in
    print(value) //[Optional(42), Optional(13), nil]
}
複製程式碼

Always

無論管道中是順利成功執行還是出現錯誤,最終都會執行always的閉包

var count = 0
let promise = Promise<Void> { _, reject in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
        reject(DYError.promise)
    })
}.always {
    count += 1
}.catch { error in
    count += 1
}.always {
    print(count)
}
複製程式碼

Any

all操作型別,但是Any中即便有其中的出現了錯誤也會返回元組資料嗎,其元組型別是Maybe,包含errorvalue相關資訊

let promise1 = Promise<Int?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
        fulfill(42)
    })
}
let promise2 = Promise<Int?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
        fulfill(13)
    })
}
let promise3 = Promise<Int?> { _, reject in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 1, execute: {
        reject(DYError.promise)
    })
}
        
let combinedPromise = any([promise1, promise2, promise3]).then { value in
    let item = value.first
    print(value.first?.value)
}
複製程式碼

Await

使用該操作,可以同步等待在不同執行緒上執行的promise。該語法與ES8中async/await的使用時類似的

Promise<Int> {
  let minusFive = try await(calculator.negate(5))
  let twentyFive = try await(calculator.multiply(minusFive, minusFive))
  let twenty = try await(calculator.add(twentyFive, minusFive))
  let five = try await(calculator.subtract(twentyFive, twenty))
  let zero = try await(calculator.add(minusFive, five))
  return try await(calculator.multiply(zero, five))
}.then { result in
  
}.catch { error in
  
}
複製程式碼

Delay

該操作返回一個預先定義的promise,等到給定的時間後執行,或者立即執行錯誤

let promise = Promise(42).delay(2)
promise.catch { err in
    print(err)
}.then { value in
    print(value)
}

DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 1, execute: {
    promise.reject(DYError.promise)
})
複製程式碼

Race

該操作與all相似,但是返回的是先執行完成的promise結果或者錯誤

let promise1 = Promise<Any?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
        fulfill(42)
    })
}
let promise2 = Promise<Any?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
        fulfill("hello world")
    })
}
let promise3 = Promise<Any?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
        fulfill([44])
    })
}
let promise4 = Promise<Any?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
        fulfill(nil)
    })
}
        
race([promise1,promise2,promise3,promise4]).then { (value) in
    print(value)
}
複製程式碼

Recover

Catch效果相同,但是該操作符不會隔斷管道中其他promise的執行

let promise = Promise<Int> { _, reject in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
        reject(DYError.promise)
    })
}.recover { error -> Promise<Int> in
    print(error)
    return Promise { fulfill, _ in
            DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
                    fulfill(1)
             })
    }
}.catch { err in
    print(err)
}.then { value in
    print(value)
}
複製程式碼

Reduce

結合其他資料進行變換

let numbers = [1, 2, 3]
Promise("0").reduce(numbers) { partialString, nextNumber in
  Promise(partialString + ", " + String(nextNumber))
}.then { string in
  // Final result = 0, 1, 2, 3
  print("Final result = \(string)")
}
複製程式碼

Retry

promise執行失敗時,嘗試重新執行。在未設定時間的前提下,預設延遲1s重新執行

var count = 0
retry(attempts: 3, delay: 5, condition: { (num, error) -> Bool in
    print("\(num)" + error.localizedDescription)
    if num == 2 {
        //馬上執行錯誤
        return false
    }
    return true
}) { () -> Promise<Int> in
    return count == 3 ? Promise(42) : Promise(DYError.promise)
}.then { (value) in
    print(value)
}.catch { (error) in
    print(error)
}
複製程式碼

Timeout

在指定的時間內如果沒有執行完成或者傳送錯誤,則自發錯誤

let promise = Promise { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
        fulfill(42)
    })
}.timeout(1).catch { err in
    print(err) //timedOut
}.then { value in
    print(value) 
}
複製程式碼

Validate

判斷是否有效,如果無效則自發錯誤

let promise = Promise { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
        fulfill(42)
    })
}.validate { value in
    return value == 1
}.catch { err in
    print(err) //validationFailure
}.then { value in
    print(value)
}
複製程式碼

相關文章