Home Promises 的 Objective-C 实现
Post
Cancel

Promises 的 Objective-C 实现

https://github.com/google/promises 是 Google 开源的 Promise 库,支持 Objective-C 和 Swift , podspec 分为 PromisesObjC.podspecPromisesSwift.podspec ,可以看到 PromisesSwift.podspec 的 dependency 为 PromisesObjC 。下面会从 Promises 的 Objective-C 版基础用法开始解析源码。

创建 Promises

PromiseState 的定义:

1
2
3
4
5
typedef NS_ENUM(NSInteger, FBLPromiseState) {
  FBLPromiseStatePending = 0,
  FBLPromiseStateFulfilled,
  FBLPromiseStateRejected,
};

FBLPromiseObserver 则定义了 Observer 的相关 block :

1
typedef void (^FBLPromiseObserver)(FBLPromiseState state, id __nullable resolution);

gFBLPromiseDefaultDispatchQueue 是默认的处理队列,在 FBLPromiseinitialize 方法中进行初始化:

1
2
3
4
5
+ (void)initialize {
  if (self == [FBLPromise class]) {
    gFBLPromiseDefaultDispatchQueue = dispatch_get_main_queue();
  }
}

FBLPromiseinitialize 方法中对 gFBLPromiseDefaultDispatchQueue 进行初始化,这样就可以在 FBLPromise 第一次调用方法前就自动设置,至于为什么要判断 self 是否为 FBLPromise class ,可以看下 initialize 的解析。

FBLPromise 的对象属性如下:

1
2
3
4
5
6
7
8
9
10
11
12
@implementation FBLPromise {
  // Promise 当前的状态
  FBLPromiseState _state;
  // 对处理中的对象进行强引用,处理完之后会自动置 nil
  NSMutableSet *__nullable _pendingObjects;
  // fulfilled 时对应的值
  id __nullable _value;
  // rejected 时对应的 错误
  NSError *__nullable _error;
  // 对应的 Observers ,当 Promise 处理完之后就会调用
  NSMutableArray<FBLPromiseObserver> *_observers;
}

创建 Promises 有两种方式,一种是还没知道结果,依赖于后续的异步操作,一种是已经知道结果,是值或者错误。

initPending 是还没知道结果,依赖于后续异步操作的初始化方法: (**instancetype**)initPending NS_SWIFT_UNAVAILABLE("")NS_SWIFT_UNAVAILABLE 表明这个方法在 Swift 中是不可用的: making_objective-c_apis_unavailable_in_swift

1
2
3
4
5
6
7
- (instancetype)initPending {
  self = [super init];
  if (self) {
    dispatch_group_enter(FBLPromise.dispatchGroup);
  }
  return self;
}

这里调用了一下 dispatch_group_enter ,但是整个库的实现并没有利用到 dispatch_group 的相关特性,这里之所以使用了 dispatch_group ,是为了方便测试,具体可以查看这里 https://github.com/google/promises/issues/13 的讨论。

initWithResolution: 则是初始化时就知道对应的结果,不依赖后续的异步操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (instancetype)initWithResolution:(nullable id)resolution {
  self = [super init];
  if (self) {
    // 根据 resolution 是否为 NSError 进行区分
    if ([resolution isKindOfClass:[NSError class]]) {
      _state = FBLPromiseStateRejected;
      _error = (NSError *)resolution;
    } else {
      _state = FBLPromiseStateFulfilled;
      _value = resolution;
    }
  }
  return self;
}

同时为了方便调用,也通过 block 的方式来提供点语法的调用:

1
2
3
4
5
6
7
8
9
10
11
+ (instancetype (^)(void))pending {
  return ^(void) {
    return [self pendingPromise];
  };
}

+ (instancetype (^)(id __nullable))resolved {
  return ^(id resolution) {
    return [self resolvedWith:resolution];
  };
}

使用方式:

1
2
FBLPromise.pending()
FBLPromise.resolved(value)

FBLPromise 提供了 fulfill: 方法,可以在异步操作成功后处理对应的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)fulfill:(nullable id)value {
  // 由于 value 是 id 类型,所以这里加多了个是否为 NSError 的判断
  if ([value isKindOfClass:[NSError class]]) {
    [self reject:(NSError *)value];
  } else {
    @synchronized(self) {
      // 只有是 FBLPromiseStatePending 状态才可以转换为 FBLPromiseStateFulfilled
      if (_state == FBLPromiseStatePending) {
        _state = FBLPromiseStateFulfilled;
        _value = value;
        _pendingObjects = nil;
        // 通知 Observers
        for (FBLPromiseObserver observer in _observers) {
          observer(_state, _value);
        }
        _observers = nil;
        dispatch_group_leave(FBLPromise.dispatchGroup);
      }
    }
  }
}

FBLPromise 为了线程安全,不少方法都加上了 @synchronized(self) ,性能上可能会有点差距。可以考虑使用下 pthread_mutex ,具体可以看 Lock 的解析。

FBLPromise 也提供了 reject: 方法,异步操作可以在由错误生成时调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)reject:(NSError *)error {
  NSAssert([error isKindOfClass:[NSError class]], @"Invalid error type.");
  
  if (![error isKindOfClass:[NSError class]]) {
    // Release 模式下调用 throw 抛出错误
    @throw error;  // NOLINT
  }
  @synchronized(self) {
    // 
    if (_state == FBLPromiseStatePending) {
      _state = FBLPromiseStateRejected;
      _error = error;
      _pendingObjects = nil;
      for (FBLPromiseObserver observer in _observers) {
        observer(_state, _error);
      }
      _observers = nil;
      dispatch_group_leave(FBLPromise.dispatchGroup);
    }
  }
}

结合 pendingfulfill:reject: 方法,我们就可以不通过异步 block 来进行一些异步操作:

1
2
3
4
5
6
7
FBLPromise<NSString *> *promise = [FBLPromise pendingPromise];
// ...
if (success) {
	[promise fulfill:@"Hello world"];
} else {
	[promise reject:someError];
}

也可以直接生成已经处理完成的 FBLPromise :

1
2
3
4
5
6
- (FBLPromise<NSData *> *) getDataAtURL:(NSURL *)anURL {
	if (anURL.absoluteString.length == 0) {
		return [FBLPromise resolvedWith:nil];
  }
	return [self loadURL:anURL];
}

FBLPromise 对内有提供添加 Observer 的方法,可以分别处理 fulfillreject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- (void)observeOnQueue:(dispatch_queue_t)queue
               fulfill:(FBLPromiseOnFulfillBlock)onFulfill
                reject:(FBLPromiseOnRejectBlock)onReject {
  @synchronized(self) {
    switch (_state) {
      // 如果是在 FBLPromiseStatePending 状态,就添加到 Observers 中
      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;
      }
      // 如果当前是 FBLPromiseStateFulfilled 状态,直接调用 onFulfill 处理 _value 即可
      case FBLPromiseStateFulfilled: {
        dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{
          onFulfill(self->_value);
        });
        break;
      }
      // 如果当前是 FBLPromiseStateRejected 状态,直接调用 onnReject 处理 _error 即可
      case FBLPromiseStateRejected: {
        dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{
          onReject(self->_error);
        });
        break;
      }
    }
  }
}

FBLPromise 也提供了链式调用的方法,虽然说 Objective-C 的 [] 方法调用会使得链式调用写起来比较啰嗦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- (FBLPromise *)chainOnQueue:(dispatch_queue_t)queue
              chainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfill
               chainedReject:(FBLPromiseChainedRejectBlock)chainedReject {
  FBLPromise *promise = [[FBLPromise alloc] initPending];
  // resolver 的 block
  __auto_type resolver = ^(id __nullable value) {
    // 判断一下 value 是否为 FBLPromise 
    // 如果是 FBLPromise ,就添加一个 Observer ,在这个 FBLPromise 的回调中进行处理
    if ([value isKindOfClass:[FBLPromise class]]) {
      [(FBLPromise *)value observeOnQueue:queue
          fulfill:^(id __nullable value) {
            [promise fulfill:value];
          }
          reject:^(NSError *error) {
            [promise reject:error];
          }];
    } else {
      // 如果不是 FBLPromise ,就直接调用 fulfill: 方法
      // fulfill 内部会判断 value 是否为 error ,所以这里不用进行区分
      [promise fulfill:value];
    }
  };
  // 添加 Observer 到 self
  [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;
}

FBLPromise 最基本的方法已经介绍完毕,通过 FBLPromise 的这些方法可以将一些异步操作转换为 Promise 的形式,而 Promise 这个库在基础的方法上又提供了一些扩展。

基础用法

Async

通过 async 操作符,可以直接在 block 中异步处理任务,然后调用 fulfill() 或者 reject()

1
2
3
4
5
6
7
8
9
10
FBLPromise<NSString *> *promise = [FBLPromise onQueue:dispatch_get_main_queue()
                                                async:^(FBLPromiseFulfillBlock fulfill,
                                                        FBLPromiseRejectBlock reject) {
  // 异步执行任务
  if (success) {
    fulfill(@"Hello world.");
  } else {
    reject(someError);
  }
}];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
typedef void (^FBLPromiseFulfillBlock)(Value __nullable value) NS_SWIFT_UNAVAILABLE("");
typedef void (^FBLPromiseRejectBlock)(NSError *error) NS_SWIFT_UNAVAILABLE("");
typedef void (^FBLPromiseAsyncWorkBlock)(FBLPromiseFulfillBlock fulfill,
                                         FBLPromiseRejectBlock reject) NS_SWIFT_UNAVAILABLE("");

+ (instancetype)onQueue:(dispatch_queue_t)queue async:(FBLPromiseAsyncWorkBlock)work {
  FBLPromise *promise = [[FBLPromise alloc] initPending];
  dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{
    // block 的参数还是两个 block
    work(
        // FBLPromiseFulfillBlock
        ^(id __nullable value) {
          // 返回的 value 是 FBLPromise ,就添加 Observer ,然后在 FBLPromise 的结果处理中调用原来 FBLPromise 的方法
          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];
          }
        },
        // FBLPromiseRejectBlock
        ^(NSError *error) {
          [promise reject:error];
        });
  });
  return promise;
}

Do

如果任务不需要异步执行,可以使用 do 操作符,它比 async 更加简介:

1
2
3
4
FBLPromise<NSString *> *promise = [FBLPromise do:^id {
  // Called asynchronously on the default queue.
  return success ? @"Hello world" : someError;
}];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+ (instancetype)onQueue:(dispatch_queue_t)queue do:(FBLPromiseDoWorkBlock)work {
  FBLPromise *promise = [[FBLPromise alloc] initPending];
  dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{
    // 和 async 不同,这里直接调用 work() block 获取结果
    id value = work();
    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];
    }
  });
  return promise;
}

由于 Objective-C 不是强类型语言,这里只能通过判断返回的 value 是否为 FBLPromise 来进行不同的处理。

Then

then 操作符支持将 FBLPromise 转换为另一个 FBLPromise 或者 value 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FBLPromise<NSNumber *> *numberPromise = [FBLPromise resolvedWith:@42];

// 返回新的 FBLPromise
FBLPromise<NSString *> *chainedStringPromise = [numberPromise then:^id(NSNumber *number) {
  return [self stringFromNumber:number];
}];

// 返回 value
FBLPromise<NSString *> *chainedStringPromise = [numberPromise then:^id(NSNumber *number) {
  return [number stringValue];
}];

// 返回错误
FBLPromise<NSString *> *chainedStringPromise = [numberPromise then:^id(NSNumber *number) {
  return [NSError errorWithDomain:@"" code:0 userInfo:nil];
}];

// 假的 void return ,可以返回 nil 或者直接返回同样的 value
FBLPromise<NSString *> *chainedStringPromise = [numberPromise then:^id(NSNumber *number) {
  NSLog(@"%@", number);
  return nil;
  // OR
  return number;
}];

then 的实现比较简单,只是直接调用了 chainOnQueue:chainedFulfill:chainedReject:

1
2
3
4
- (FBLPromise *)onQueue:(dispatch_queue_t)queue then:(FBLPromiseThenWorkBlock)work {
  // error 部分会在新的 FBLPromise 中处理,所以这里的 chainedReject 传 nil 就可以了
  return [self chainOnQueue:queue chainedFulfill:work chainedReject:nil];
}

由于 Objective-C 不支持方法重载,所以这里不能提供一个 void 返回的 then 操作符版本,只能通过返回 nil 或者返回原有的值来达到相同的目的,建议返回原有的值,后续还可以继续通过 then 来进行链式调用。通过 then 来进行链式调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (FBLPromise<NSString *> *)work1:(NSString *)string {
	return [FBLPromise do:^id {
		return string;
	}];
}

- (FBLPromise<NSNumber *> *)work2:(NSString *)string {
  return [FBLPromise do:^id {
    return @(string.integerValue);
  }];
}

- (NSNumber *)work3:(NSNumber *)number {
  return @(number.integerValue * number.integerValue);
}

[[[[self work1:@"10"] then:^id(NSString *string) {
  return [self work2:string];
}] then:^id(NSNumber *number) {
  return [self work3:number];
}] then:^id(NSNumber *number) {
  NSLog(@"%@", number);  // 100
  return number;
}];

Catch

catch 操作符和 then 相反,只处理错误部分,同时也会隐式得返回新的 FBLPromise

1
2
3
[[self numberFromString:@"abc"] catch:^(NSError *error) {
  NSLog(@"Cannot convert string to number: %@", error);
}];

通过 catchthen 结合,可以不需要在每次异步操作时都需要进行错误处理 ,而在上层统一处理错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (FBLPromise<NSString *> *)work1:(NSString *)string {
  return [FBLPromise do:^id {
    return string;
  }];
}

- (FBLPromise<NSNumber *> *)work2:(NSString *)string {
  return [FBLPromise do:^id {
    NSInteger number = string.integerValue;
    return number > 0 ? @(number) : [NSError errorWithDomain:@"" code:0 userInfo:nil];
  }];
}

- (NSNumber *)work3:(NSNumber *)number {
  return @(number.integerValue * number.integerValue);
}

[[[[[self work1:@"abc"] then:^id(NSString *string) {
  return [self work2:string];
}] then:^id(NSNumber *number) {
  return [self work3:number];  // Never executed.
}] then:^id(NSNumber *number) {
  NSLog(@"%@", number);  // Never executed.
  return number;
}] catch:^(NSError *error) {
  NSLog(@"Cannot convert string to number: %@", error);
}];

进阶

通过上面的 asyncdothencatch 操作符,就可以使用 FBLPromise 实现大多数异步操作序列。如果需要开箱即用的模式,这里还有一些进阶操作符。

All

all 操作符是由类方法提供,它会等待数组中所有的 FBLPromise 都变为 fulfilled 后才会调用 fulfill: 方法。只要有其中一个 FBLPromiserejected 了, all 方法生成的 FBLPromise 都会收到相同的错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 相同类型的 FBLPromise
[[FBLPromise all:[contacts fbl_map:^id(MyContact *contact) {
  return [MyClient getAvatarForContact:contact];
}]] then:^id(NSArray<UIImage *> *avatars) {
  [self updateAvatars:avatars];
  return nil;
}];

// 不同类型的 FBLPromise
[[FBLPromise
    all:@[ [MyClient getLocationForContact:contact], [MyClient getAvatarForContact:contact] ]]
    then:^id(NSArray *locationAndAvatar) {
      [self updateContactLocation:locationAndAvatar.firstObject
                        andAvatar:locationAndAvatar.lastObject];
      return nil;
    }];

由于 Objective-C 不是强类型语言,所以调用 all 时需要非常小心,避免传输错误的类型。上面的 Objective-C 代码使用了 NSArrayfbl_map 方法,这个是 Google 出的一个为 Objective-C 补上缺少的函数式操作符的库:https://github.com/google/functional-objc 。

all 方法的实现也比较朴实无华,我本来以为会用到 dispatch_group 之类的,没想到并没有,只是用一个 for 循环,在每次更新数组的 FBLPromise 状态时判断是否所有 FBLPromise 都已经完成处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
+ (FBLPromise<NSArray *> *)onQueue:(dispatch_queue_t)queue all:(NSArray *)allPromises {
  // 如果 allPromises 为空,就直接返回
  if (allPromises.count == 0) {
    return [[FBLPromise alloc] initWithResolution:@[]];
  }
  NSMutableArray *promises = [allPromises mutableCopy];
  return [FBLPromise
      onQueue:queue
        async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) {
          // 这里需要处理传进来的数据,如果不是 FBLPromise 则将它转换为 FBLPromise 
          for (NSUInteger i = 0; i < promises.count; ++i) {
            id promise = promises[i];
            if ([promise isKindOfClass:self]) {
              continue;
            } else if ([promise isKindOfClass:[NSError class]]) {
              // 如果是 NSError 就直接调用 reject 方法。
              reject(promise);
              return;
            } else {
              [promises replaceObjectAtIndex:i
                                  withObject:[[FBLPromise alloc] initWithResolution:promise]];
            }
          }
          for (FBLPromise *promise in promises) {
            [promise observeOnQueue:queue
                fulfill:^(id __unused _) {
                  // 每个 promise fulfill 时都判断下是否还有 promise 没处理完,
							    // 时间复杂度 O(n) ,但是一般 promise 数组的数量也不会太多,所以影响不大
                  for (FBLPromise *promise in promises) {
                    if (!promise.isFulfilled) {
                      return;
                    }
                  }
                  // 通过 key-value 的方式来返回 promises 的 value 数组
                  fulfill([promises valueForKey:NSStringFromSelector(@selector(value))]);
                }
                reject:^(NSError *error) {
                  reject(error);
                }];
          }
        }];
}

Always

always 操作符用于在 Promise 管道上执行一些代码,不管这个 Promise 是 fulfilled 还是 rejected

1
2
3
4
5
6
7
8
[[[[self getCurrentUserContactsAvatars] then:^id(NSArray<UIImage *> *avatars) {
  [self updateAvatars:avatars];
  return avatars;
}] catch:^(NSError *error) {
  [self showErrorAlert:error];
}] always:^{
  self.label.text = @"All done.";
}];

always 是通过 chainOnQueue:chainedFulfill:chainedReject: 来实现的,以此来生成新的 FBLPromise ,和在 filfilledrejected 中都调用 work

1
2
3
4
5
6
7
8
9
10
11
- (FBLPromise *)onQueue:(dispatch_queue_t)queue always:(FBLPromiseAlwaysWorkBlock)work {
  return [self chainOnQueue:queue
      chainedFulfill:^id(id value) {
        work();
        return value;
      }
      chainedReject:^id(NSError *error) {
        work();
        return error;
      }];
}

Any

any 操作符和 all 类似,但是只有所有 promises 都是 rejected 才会调用 reject 方法,只要有一个 promise 是 fulfilled 都会调用 fulfill 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 获取 promises 中的 value 或者 error
static NSArray *FBLPromiseCombineValuesAndErrors(NSArray<FBLPromise *> *promises) {
  NSMutableArray *combinedValuesAndErrors = [[NSMutableArray alloc] init];
  for (FBLPromise *promise in promises) {
    if (promise.isFulfilled) {
      [combinedValuesAndErrors addObject:promise.value ?: [NSNull null]];
      continue;
    }
    if (promise.isRejected) {
      [combinedValuesAndErrors addObject:promise.error];
      continue;
    }
    assert(!promise.isPending);
  };
  return combinedValuesAndErrors;
}

+ (FBLPromise<NSArray *> *)onQueue:(dispatch_queue_t)queue any:(NSArray *)anyPromises {
  if (anyPromises.count == 0) {
    return [[FBLPromise alloc] initWithResolution:@[]];
  }
  NSMutableArray *promises = [anyPromises mutableCopy];
  return [FBLPromise
      onQueue:queue
        async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) {
          // 照样转换为 FBLPromise
          for (NSUInteger i = 0; i < promises.count; ++i) {
            id promise = promises[i];
            if ([promise isKindOfClass:self]) {
              continue;
            } else {
              [promises replaceObjectAtIndex:i
                                  withObject:[[FBLPromise alloc] initWithResolution:promise]];
            }
          }
          for (FBLPromise *promise in promises) {
            [promise observeOnQueue:queue
                fulfill:^(id __unused _) {
                  // 判断是否还有 promise 在处理中
                  for (FBLPromise *promise in promises) {
                    if (promise.isPending) {
                      return;
                    }
                  }
                  // 所有 promises 都处理完毕后,调用 fulfill 
                  fulfill(FBLPromiseCombineValuesAndErrors(promises));
                }
                reject:^(NSError *error) {
                  BOOL atLeastOneIsFulfilled = NO;
                  for (FBLPromise *promise in promises) {
                    if (promise.isPending) {
                      return;
                    }
                    if (promise.isFulfilled) {
                      atLeastOneIsFulfilled = YES;
                    }
                  }
                  // 数组中只要有一个 promise 是 fulfilled ,就调用 fulfill 
                  if (atLeastOneIsFulfilled) {
                    fulfill(FBLPromiseCombineValuesAndErrors(promises));
                  } else {
                    reject(error);
                  }
                }];
          }
        }];
}

AwaitPromise

使用 awaitPromise 操作符,可以通过同步的方式来等待不同线程中的 promise 完成处理。当需要从多个异步操作中以不同的方式混合多个操作结果时,就可以使用 awaitPromise 来进行操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[[[FBLPromise do:^id {
  NSError *error;
  NSNumber *minusFive = FBLPromiseAwait([calculator negate:@5], &error);
  if (error) return error;
  NSNumber *twentyFive = FBLPromiseAwait([calculator multiply:minusFive by:minusFive], &error);
  if (error) return error;
  NSNumber *twenty = FBLPromiseAwait([calculator add:twentyFive to:minusFive], &error);
  if (error) return error;
  NSNumber *five = FBLPromiseAwait([calculator subtract:twentyFive from:twenty], &error);
  if (error) return error;
  NSNumber *zero = FBLPromiseAwait([calculator add:minusFive to:five], &error);
  if (error) return error;
  NSNumber *result = FBLPromiseAwait([calculator multiply:zero by:five], &error);
  if (error) return error;
  return result;
}] then:^id(NSNumber *result) {
  // ...
}] catch:^(NSError *error) {
  // ...
}];

FBLPromiseAwait 通过 dispatch_semaphore_t 来将异步操作强制为同步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
id __nullable FBLPromiseAwait(FBLPromise *promise, NSError **outError) {
  static dispatch_once_t onceToken;
  static dispatch_queue_t queue;
  dispatch_once(&onceToken, ^{
    queue = dispatch_queue_create("com.google.FBLPromises.Await", DISPATCH_QUEUE_CONCURRENT);
  });
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  id __block resolution;
  NSError __block *blockError;
  [promise chainOnQueue:queue
      chainedFulfill:^id(id value) {
        resolution = value;
        dispatch_semaphore_signal(semaphore);
        return value;
      }
      chainedReject:^id(NSError *error) {
        blockError = error;
        dispatch_semaphore_signal(semaphore);
        return error;
      }];
  // 等待 promise 调用 fulfill 或者 reject
  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  if (outError) {
    *outError = blockError;
  }
  return resolution;
}

Delay

delay 操作符支持在 fulfill 中加入延迟时间,或者直接调用 reject ,可以用来在 promise 链式调用中添加人为的暂停:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (FBLPromise *)onQueue:(dispatch_queue_t)queue delay:(NSTimeInterval)interval {
  FBLPromise *promise = [[FBLPromise alloc] initPending];
  [self observeOnQueue:queue
      fulfill:^(id __nullable value) {
        dispatch_after(dispatch_time(0, (int64_t)(interval * NSEC_PER_SEC)), queue, ^{
          [promise fulfill:value];
        });
      }
      reject:^(NSError *error) {
        [promise reject:error];
      }];
  return promise;
}

Race

race 操作符和 all 相似,但是它会采用第一个完成处理的 promise 的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+ (instancetype)onQueue:(dispatch_queue_t)queue race:(NSArray *)racePromises {
  NSArray *promises = [racePromises copy];
  return [FBLPromise onQueue:queue
                       async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) {
                         for (id promise in promises) {
                           if (![promise isKindOfClass:self]) {
                             fulfill(promise);
                             return;
                           }
                         }
                         // 虽然订阅了所有的 promise ,
                         // 但是只有第一个完成处理的 promise 开业改变所返回的 promise 的状态
                         for (FBLPromise *promise in promises) {
                           [promise observeOnQueue:queue fulfill:fulfill reject:reject];
                         }
                       }];
}

Recover

recover 操作符允许我们 catch 错误,不会破坏 promise 的链式调用。

1
2
3
4
5
6
7
[[[self getCurrentUserContactsAvatars] recover:^id(NSError *error) {
  NSLog(@"Fallback to default avatars due to error: %@", error);
  return [self getDefaultsAvatars];
}] then:^id(NSArray<UIImage *> *avatars) {
  [self updateAvatars:avatars];
  return avatars;
}];

实现也比较简单,在链式调用中加多一个中间层的处理:

1
2
3
4
5
6
7
8
- (FBLPromise *)onQueue:(dispatch_queue_t)queue recover:(FBLPromiseRecoverWorkBlock)recovery {
  return [self chainOnQueue:queue
             chainedFulfill:nil
              chainedReject:^id(NSError *error) {
                // 调用 recovery 对 error 进行转换
                return recovery(error);
              }];
}

Reduce

reduce 操作符使得可以指定 block 来从 promises 集合中生成单个值。比如说将数字数组转换为单个字符串:

1
2
3
4
5
6
7
8
9
NSArray<NSNumber *> *numbers = @[ @1, @2, @3 ];
[[[FBLPromise resolvedWith:@"0"] reduce:numbers
                                combine:^id(NSString *partialString, NSNumber *nextNumber) {
  return [NSString stringWithFormat:@"%@, %@", partialString, nextNumber.stringValue];
}] then:^id(NSString *string) {
  // Final result = 0, 1, 2, 3
  NSLog(@"Final result = %@", string);
  return nil;
}];
1
2
3
4
5
6
7
8
9
10
11
12
13
- (FBLPromise *)onQueue:(dispatch_queue_t)queue
                 reduce:(NSArray *)items
                combine:(FBLPromiseReducerBlock)reducer {
  FBLPromise *promise = self;
  for (id item in items) {
    promise = [promise chainOnQueue:queue
                     chainedFulfill:^id(id value) {
                       return reducer(value, item);
                     }
                      chainedReject:nil];
  }
  return promise;
}

Retry

retry 操作符提供了 promise 相关任务在失败时重试的灵活性。默认情况下, retry 操作符会在第一次 reject 后延迟一秒进行重试。如果默认操作不满足自己的需求,可以自定义队列,最大重试次数,延迟时间间隔以及不满足条件时提前退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- (FBLPromise<NSData *, NSURLResponse *> *)fetchWithURL:(NSURL *)url {
  return [FBLPromise wrap2ObjectsOrErrorCompletion:^(FBLPromise2ObjectsOrErrorCompletion handler) {
    [NSURLSession.sharedSession dataTaskWithURL:url completionHandler:handler];
  }];
}

NSURL *url = [NSURL URLWithString:@"https://myurl.com"];

// 默认延迟 1s
[[[FBLPromise retry:^id {
  return [self fetchWithURL:url];
}] then:^id(NSArray *values) {
  NSLog(@"%@", values);
  return nil;
}] catch:^(NSError *error) {
  NSLog(@"%@", error);
}];

// 自定义队列,最大尝试次数,延迟时间和是否提前退出
dispatch_queue_t customQueue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
[[[FBLPromise onQueue:customQueue
    attempts:5
    delay:2.0
    condition:^BOOL(NSInteger remainingAttempts, NSError *error) {
      return error.code == NSURLErrorNotConnectedToInternet;
    }
    retry:^id {
      return [self fetchWithURL:url];
}] then:^id(NSArray *values) {
  // 当 retry 成功时就会调用 then
  NSLog(@"%@", values);
  return nil;
}] catch:^(NSError *error) {
  // 不满足 retry 条件时就会抛出 error 
  NSLog(@"%@", error);
}];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static void FBLPromiseRetryAttempt(FBLPromise *promise, dispatch_queue_t queue, NSInteger count,
                                   NSTimeInterval interval, FBLPromiseRetryPredicateBlock predicate,
                                   FBLPromiseRetryWorkBlock work) {
  __auto_type retrier = ^(id __nullable value) {
    if ([value isKindOfClass:[NSError class]]) {
      // 如果 count <= 0 或者不满足重试条件,就不进行重试
      if (count <= 0 || (predicate && !predicate(count, value))) {
        [promise reject:value];
      } else {
        // 延迟指定时间后重试, count - 1
        dispatch_after(dispatch_time(0, (int64_t)(interval * NSEC_PER_SEC)), queue, ^{
          FBLPromiseRetryAttempt(promise, queue, count - 1, interval, predicate, work);
        });
      }
    } else {
      // 如果不是 error ,就调用 fulfill:
      [promise fulfill:value];
    }
  };
  id value = work();
  //  如果 work() 处理之后是 FBLPromise ,就添加对应的监听
  if ([value isKindOfClass:[FBLPromise class]]) {
    [(FBLPromise *)value observeOnQueue:queue fulfill:retrier reject:retrier];
  } else  {
    retrier(value);
  }
}

+ (FBLPromise *)onQueue:(dispatch_queue_t)queue
               attempts:(NSInteger)count
                  delay:(NSTimeInterval)interval
              condition:(nullable FBLPromiseRetryPredicateBlock)predicate
                  retry:(FBLPromiseRetryWorkBlock)work {
  FBLPromise *promise = [[FBLPromise alloc] initPending];
  FBLPromiseRetryAttempt(promise, queue, count, interval, predicate, work);
  return promise;
}

Timeout

timeout 操作符支持在等待指定时间后,如果 promise 还没完成,就主动调用 reject ,错误类型为 FBLPromiseErrorCodeTimedOut

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (FBLPromise *)onQueue:(dispatch_queue_t)queue timeout:(NSTimeInterval)interval {
  FBLPromise *promise = [[FBLPromise alloc] initPending];
  [self observeOnQueue:queue
      fulfill:^(id __nullable value) {
        [promise fulfill:value];
      }
      reject:^(NSError *error) {
        [promise reject:error];
      }];
  typeof(self) __weak weakPromise = promise;
  // 超过指定时间后就主动调用 reject
  dispatch_after(dispatch_time(0, (int64_t)(interval * NSEC_PER_SEC)), queue, ^{
    NSError *timedOutError = [[NSError alloc] initWithDomain:FBLPromiseErrorDomain
                                                        code:FBLPromiseErrorCodeTimedOut
                                                    userInfo:nil];
    [weakPromise reject:timedOutError];
  });
  return promise;
}

Validate

validate 操作符使得可以对值进行检查而不破坏 promise 的链式调用。它和 then 类似,接收 promise 解析后的值,然后返回一个 bool 值。如果返回 false ,就会抛出一个错误,错误类型为 FBLPromiseErrorCodeValidationFailure

1
2
3
4
5
6
7
[[[[self getAuthToken] validate:^BOOL(NSString *authToken) {
  return authToken.length > 0;
}] then:^id(NSString *authToken) {
  return [self getDataWithToken:authToken];
}] catch:^(NSError *error) {
  NSLog(@"Failed to get auth token: %@", error);
}];

validate 的实现,实现也比较简单:

1
2
3
4
5
6
7
8
9
10
11
- (FBLPromise*)onQueue:(dispatch_queue_t)queue validate:(FBLPromiseValidateWorkBlock)predicate {
  
  FBLPromiseChainedFulfillBlock chainedFulfill = ^id(id value) {
    // predicate 判断下,如果不满足就抛出 FBLPromiseErrorCodeValidationFailure
    return predicate(value) ? value :
                              [[NSError alloc] initWithDomain:FBLPromiseErrorDomain
                                                         code:FBLPromiseErrorCodeValidationFailure
                                                     userInfo:nil];
  };
  return [self chainOnQueue:queue chainedFulfill:chainedFulfill chainedReject:nil];
}

Wrap

wrap 操作符提供了一个便利的转换方式,支持将比较通用的回调,比如说 ^(id, NSError *) 转换为 FBLPromise

1
2
3
4
5
- (FBLPromise<NSData*> *)newAsyncMethodReturningAPromise {
  return [FBLPromise wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion handler) {
    [MyClient wrappedAsyncMethodWithTypicalCompletion:handler];
  }];
}

wrap 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef void (^FBLPromiseObjectOrErrorCompletion)(id __nullable, NSError* __nullable);

+ (instancetype)onQueue:(dispatch_queue_t)queue
    wrapObjectOrErrorCompletion:(void (^)(FBLPromiseObjectOrErrorCompletion))work {
  return [self onQueue:queue
                 async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) {
                   // 调用 work 来获取异步操作的结果,然后调用 FBLPromise 对应的方法
                   work(^(id __nullable value, NSError *__nullable error) {
                     if (error) {
                       reject(error);
                     } else {
                       fulfill(value);
                     }
                   });
                 }];
}

其它一些需要注意的问题

默认的 GCD 队列

Promises 内部使用 GCD 来派发不同的任务到不同的队列。如果没有指定,默认是主队列,为了避免造成主队列过于忙碌,影响用户的体验,通常会调整默认队列:

1
FBLPromise.defaultDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

循环引用

对于 promise 来说,你几乎不需要关心它们的循环引用。当 promise 完成任务时,它会释放掉所有 block 的强引用。尽管如此,还是有可能会产生循环引用。比如给说在 promise 的链式调用 block 中使用 promise 对象,如果你已经 promise 保存在一个局部变量中,就可能会发生循环引用,特别是创建一个不关联任何 block 的未完成解析的 promise 时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@implementation MyClass {
  FBLPromise<NSNumber *> *_promise;
}

- (FBLPromise<NSString *> *)doSomething {
  if (_promise == nil) {
    _promise = [FBLPromise pendingPromise];
  }
  return [_promise then:^id(id number) {
    return [self doSomethingElse:number];
  }];
}

- (NSString *)doSomethingElse:(NSNumber *)number {
  return number.stringValue;
}

@end

self 持有 promise ,而 promise 又在 then block 中捕获 self 。我们可以通过 weak 引用来解决,因为我们知道 MyClass 的代码细节,但是这种情况有可能变得更加难以察觉:

1
2
3
[[myClass doSomething] then:^id(NSString *string) {
  [myClass doSomeOtherThing];
}];

我们通过 MyClass 的方法来获取一个 promise ,然后通过链式调用来添加监听 block ,在里面再捕获 MyClass 的对象,这样也会产生循环引用。这部分代码并不知道 MyClass 会对 promise 进行强引用,而且还没有对应的 reslove 代码,所以这个循环引用不会被打破。

对于 Promise 的循环引用来说,它和我们日常开发中遇到的循环引用情况差不多,并没有银弹,我们需要单独考虑每种情况。尽量避免使用 pending 的 promises ,一旦 promise 完成解析,就调用对应的方法。

Objective-C 的点语法

当在 Objective-C 中使用链式调用时,就会出现大量的方括号和其它一些格式问题,对于每个操作符, FBLPromise 都通过 block 的方式提供了点语法的调用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[self work1:@"abc"]
    .then(^id(NSString *string) {
      return [self work2:string];
    })
    .then(^id(NSNumber *number) {
      return [self work3:number];
    })
    .then(^id(NSNumber *number) {
      NSLog(@"%@", number);
      return nil;
    })
    .catch(^(NSError *error) {
      NSLog(@"Cannot convert string to number: %@", error);
    });

总结

Promises 的 Objective-C 层实现比较简单,也比较小巧,代码量不多,主要是按照 Promise 的规则来,结合 GCD 提供符合定义的接口就好来。

This post is licensed under CC BY 4.0 by the author.

Promise - 介绍

Promises 的 Swift 实现

Comments powered by Disqus.