先来看一个开发中常见的嵌套类型的例子:
//一般的block回调嵌套
- (void) _normalExample
{
[self _normalBlock1:^(id value) {
[self _normalBlock2:^(id value) {
[self _normalBlock3:^(id value) {
[self _normalBlock4:^(id value) {
[self _normalBlock5:^(id value) {
[self _normalBlock6:^(id value) {
}];
}];
}];
}];
}];
}];
}
//正常blocks
- (void) _normalBlock1:(void(^)(id value))block
{
NSLog(@"_normalBlock1");
block(@(1));
}
- (void) _normalBlock2:(void(^)(id value))block
{
block(@"_normalBlock2");
}
- (void) _normalBlock3:(void(^)(id value))block
{
block(@"_normalBlock3");
}
- (void) _normalBlock4:(void(^)(id value))block
{
block(@"_normalBlock4");
}
- (void) _normalBlock5:(void(^)(id value))block
{
block(@"_normalBlock5");
}
- (void) _normalBlock6:(void(^)(id value))block
{
block(@"_normalBlock6");
}
有没有被上面的嵌套整蒙?当然开发中嵌套如此多层的也有但不多见,然鹅这样一坨代码写在那里终归不美观(当然你要是觉得有层次感,那我也没法,不过话题还是要继续),很不利于后期的维护和修改。
那么有没有一种方式解决呢?下面引入今天的正题Promise, 先看下用promise实现的方式:
//采用promise
- (void) _promiseExample
{
[[[[[[[FBLPromise do:^id _Nullable{
return [self _promise1];
}] then:^id _Nullable(id _Nullable value) {
return [self _promise2];
}] then:^id _Nullable(id _Nullable value) {
return [self _promise3];
}] then:^id _Nullable(id _Nullable value) {
return [self _promise4];
}] then:^id _Nullable(id _Nullable value) {
return [self _promise5];
}] then:^id _Nullable(id _Nullable value) {
return [self _promise6];
}] catch:^(NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
}
//promises
- (FBLPromise *) _promise1
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_promise1");
}];
}
- (FBLPromise *) _promise2
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_promise2");
}];
}
- (FBLPromise *) _promise3
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_promise3");
}];
}
- (FBLPromise *) _promise4
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_promise4");
}];
}
- (FBLPromise *) _promise5
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_promise5");
}];
}
- (FBLPromise *) _promise6
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_promise6");
}];
}//promises
- (FBLPromise *) _promise1
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_promise1");
}];
}
- (FBLPromise *) _promise2
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_promise2");
}];
}
- (FBLPromise *) _promise3
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_promise3");
}];
}
- (FBLPromise *) _promise4
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_promise4");
}];
}
- (FBLPromise *) _promise5
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_promise5");
}];
}
- (FBLPromise *) _promise6
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_promise6");
}];
}
在调用层面是不是感觉很舒服?而且调用的逻辑语义也显而易见,如果你觉得很爽,那么就接下来一起聊聊promise的使用方法和一些浅显的逻辑原理,如果你觉得看着也不舒服,那就继续用Block套block呗。
如何用Promise
如图为PromisesObjC的框架结构,除去FBLPromise、FBLPromiseError以及头文件,这些都是不同情形下的实现类,下面我们先从常用的几个入手,熟悉一下如何去使用promise
1. then
如字面意思,这个特别适合一件事做完再做另一件事的逻辑,如A,B两个任务,B依赖于A任务的完成结果,设置一个场景,任务B依赖任务A返回的一个值,进行求和,然后输出求和结果
- (void) _thenExample
{
[[[FBLPromise do:^id _Nullable{
return [self _thenPromise1];
}] then:^id _Nullable(id _Nullable value) {
return [self _thenPromise2:[value intValue]];
}] then:^id _Nullable(id _Nullable value) {
NSLog(@"%@",value);
return nil;
}];
}
- (FBLPromise *) _thenPromise1
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@(1));
}];
}
- (FBLPromise *) _thenPromise2:(int)input
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
int sum = input + 10;
fulfill(@(sum));
}];
}
do暂且不管,我们可以进入then方法查看源码,它会返回一个FBLPromise的对象,内部会调用[self chainOnQueue:queue chainedFulfill:work chainedReject:nil]核心方法,至于这个核心方法中的逻辑我们稍后讲解原理的时候再说,可以看到正是因为此方法返回的FBLPromise,才能实现这种链式调用的形式,当然,你可以写成点调用的形式,如下:
[FBLPromise do:^id _Nullable{
return [self _thenPromise1];
}].then(^ id (id value){
return [self _thenPromise2:[value intValue]];
}).then(^ id (id value){
NSLog(@"%@",value);
return nil;
});
但这种写法then中的参数不会自动补全,写起来比较别扭,所以,下面的例子我都以[]的形式书写,毕竟是OC的特有结构
总结:
then可用作一个任务的执行依赖另一个任务完成的结果的场景,开发中常见的是对网络图片加载完成后对图片的特殊处理,如模糊,剪切等,或者网络请求数据完成后对数据的处理等情况,这里不再赘述,有兴趣的可拿自己项目来练练。
2. all
Wait until all of the given promises are fulfilled.If one of the given promises is rejected, then the returned promise is rejected with same error.
源码文档中的解释如上:当所有的promise执行完成才执行另一个任务,只要有一个promise执行错误,则这个任务队列直接结束,并返回此错误。此外,执行完成中如果返回值是NSError的,也按照执行错误处理。简而言之,只要所有的promise都执行了fulfill()且fulfill()中的参数不能为NSError类型,才算所有的任务执行成功。
这种情形在开发中非常常见,特别是在多个任务的情形,而且这些待执行的任务顺序不分先后,但是在then中的结果返回数组中的元素,经过验证是和all中数组的顺序是一致的
假设一种场景,C任务在A,B任务都执行成功的时候才执行
/******************** all的使用 *********************/
- (void) _allExample
{
NSArray *promises = @[[self _allPromise1],[self _allPromise2]];
[[[[FBLPromise all:promises] then:^id _Nullable(NSArray * _Nullable value) {
NSLog(@"%@",value);
return [self _allPromise3];
}] then:^id _Nullable(id _Nullable value) {
NSLog(@"%@",value);
return nil;
}] catch:^(NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
}
- (FBLPromise *) _allPromise1
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
fulfill(@"_allPromise1 执行完成");
});
}];
}
- (FBLPromise *) _allPromise2
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_allPromise2 执行完成");
}];
}
- (FBLPromise *) _allPromise3
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_allPromise3 执行完成");
}];
}
输出结果如下:
可以看到
[FBLPromise all:promises] then:^id _Nullable(NSArray * _Nullable value) {
NSLog(@"%@",value);
return [self _allPromise3];
}]
promises 任务数组的顺序和返回值value的顺序保持一致性
如果上述两个任务有一个失败,则会进入catch中,这个留到后面讲解
总结:all只有当给定的所有待执行的任务全部都成功执行,且返回值不能为
NSError类型,才能继续接下来的任务执行,否则以第一个执行出错的任务的错误作为整体的错误返回,进入到catch中。
3. catch
此方法是用来捕捉错误的
/******************** catch的使用 *********************/
- (void) _catchExample
{
[[FBLPromise do:^id _Nullable{
return [self _catchPromise];
}] catch:^(NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
}
- (FBLPromise *) _catchPromise
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
NSError *error = [NSError errorWithDomain:@"catch.example" code:0 userInfo:@{}];
reject(error);
// fulfill(error);
}];
}
上述代码即可说明,这里不再详细解释
4. always
A block that always executes, no matter if the receiver is rejected or fulfilled.
源码中的注释是,无论接收者是执行成功还是失败,都会执行此block块
- (void) _alwaysExample
{
[[[[FBLPromise do:^id _Nullable{
return [self _alwaysPromise1];
}] then:^id _Nullable(id _Nullable value) {
NSLog(@"%@",value);
return [self _alwaysPromise2];
}] catch:^(NSError * _Nonnull error) {
NSLog(@"%@",error);
}] always:^{
NSLog(@"总是会执行");
}];
}
- (FBLPromise *) _alwaysPromise1
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
NSError *error = [NSError errorWithDomain:@"catch.example" code:0 userInfo:@{}];
fulfill(error);
}];
}
- (FBLPromise *) _alwaysPromise2
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_alwaysPromise2");
}];
}
输出结果如下:
总结: always是一定会执行的block块,无论前面的执行结果是成功还是失败
5. any
可以理解为
all的对立面,只有当所有的任务执行都错误后才执行catch,且捕捉到的是最后一次返回的错误,否则只要有一个任务执行成功,则执行成功。
/******************** any的使用 *********************/
- (void) _anyExample
{
[[[[FBLPromise any:@[[self _anyPromise1],[self _anyPromise2]]] then:^id _Nullable(NSArray * _Nullable value) {
return [self _anyPromise3];
}] then:^id _Nullable(id _Nullable value) {
NSLog(@"%@",value);
return value;
}] catch:^(NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
}
- (FBLPromise *) _anyPromise1
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_anyPromise1");
// NSError *error = [NSError errorWithDomain:@"any.example" code:1 userInfo:@{}];
// reject(error);
}];
}
- (FBLPromise *) _anyPromise2
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
NSError *error = [NSError errorWithDomain:@"any.example" code:0 userInfo:@{}];
reject(error);
}];
}
- (FBLPromise *) _anyPromise3
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_anyPromise3");
}];
}
经过验证会发现,只有当_anyPromise1 _anyPromise2全都执行reject的时候,才会执行catch,而不会执行第一个then中的_anyPromise3,否则一定会执行第一个then块里的内容,此外要注意的是,any中给定的promises数组必须全部执行完毕,才会继续决定执行then或者reject.
6. async
此方法创建一个
pending状态的promise,且带异步执行的block块,适合用来处理异步任务回来后的情况,比如网络请求或者耗时操作回来后要做某些事,就可以使用此方法,此方法block块中的FBLPromiseAsyncWorkBlock含有FBLPromiseFulfillBlock和FBLPromiseRejectBlock,可以用来处理异步任务回来后决定是否需要继续执行下一步任务,上面的例子中都用到了此种方式,可以自行测验,不再附代码说明
7. await
此方法是用来生成阻塞当前线程的
promise,直到当此promise执行完毕,类似于系统提供的方法sleep(),它内部其实使用了信号量实现,此方法在特殊情况下可用,一般使用比较少。
8. delay:
它内部使用了
dispatch_after实现延迟执行的效果,此法简单,不再赘述
9. race:
此方法和
any效果类似,给定的promises中只要有一个执行成功,则会执行then之后的内容,只有全部执行失败,才会执行catch,它和any的区别是,race给定的promises数组只要有一个promise执行完成,就会执行then或者catch,如果全部的promise都执行,效果和any一样
/******************** race的使用 *********************/
- (void) _raceExample
{
[[[FBLPromise race:@[[self _racePromise1],[self _racePromise2]]] then:^id _Nullable(id _Nullable value) {
return value;
}] catch:^(NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
}
- (FBLPromise *) _racePromise1
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
// fulfill(@"_racePromise1");
NSError *error = [NSError errorWithDomain:@"race.example" code:2 userInfo:@{}];
reject(error);
}];
}
- (FBLPromise *) _racePromise2
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
// NSError *error = [NSError errorWithDomain:@"race.example" code:0 userInfo:@{}];
// reject(error);
}];
}
可以对不同的promise注销fulfill reject来验证
10. recover
用一个新的
promise去替换掉执行失败的promise,而不让它执行到catch中,可谓是偷龙换凤,在某些场景下回用到,比如网络请求回来后,如果失败,我们不想让它走到catch中报错,就可以用recover去执行另一个promise
/******************** recover的使用 *********************/
- (void) _recoverExample
{
[[[[self _recoverPromise1] recover:^id _Nullable(NSError * _Nonnull error) {
if (error) {
return [self _recoverPromise2];
}
return nil;
}] then:^id _Nullable(id _Nullable value) {
return value;
}] catch:^(NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
}
- (FBLPromise *) _recoverPromise1
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
// NSError *error = [NSError errorWithDomain:@"recover.example" code:0 userInfo:@{}];
// reject(error);
fulfill(@"_recoverPromise1");
}];
}
- (FBLPromise *) _recoverPromise2
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@"_recoverPromise2");
}];
}
上面的代码可以自行测验,会更加明白此方法的效果
11. reduce:
它是
promise的一个实例方法,调用它的是一个promise对象,reduce数组是一个值数组,reduce的作用就是用promise和后面的值数组进行组合,每两个组合可得到一个promise对象,然后用新的promise对象和剩下的值进行组合,最终得到一个promise对象,在reduce的block块中我们可以拿到一个promise的返回值和另一个值,我们可以对这两个值做自己的逻辑处理,如下例子做的是乘积
/******************** reduce的使用 *********************/
- (void) _reduceExample
{
[[[[self _reducePromise1] reduce:@[@(10), @(20)] combine:^id _Nullable(id _Nullable partial, id _Nonnull next) {
return @([partial intValue] * [next intValue]);
}] then:^id _Nullable(id _Nullable value) {
return value;
}] catch:^(NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
}
- (FBLPromise *) _reducePromise1
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@(3));
}];
}
12. timeout:
设置超时时间,若执行时间大于超时时间,则会报错,进入
catch块中,否则会进入then中
/******************** timeout的使用 *********************/
- (void) _timeoutExample
{
[[[[self _timeoutPromise] timeout:2] then:^id _Nullable(id _Nullable value) {
return value;
}] catch:^(NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
}
- (FBLPromise *) _timeoutPromise
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
fulfill(@"_timeoutPromise");
});
}];
}
13. validate:
验证方法,此方法是实例方法,如果满足验证逻辑,则会进入
then,value是promise成功后的回传参数,否则会进入catch中,error代表的是validate错误
/******************** validate的使用 *********************/
- (void) _validateExample
{
[[[[self _validatePromise] validate:^BOOL(id _Nullable value) {
return ([value intValue] > 50);
}] then:^id _Nullable(id _Nullable value) {
return value;
}] catch:^(NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
}
- (FBLPromise *) _validatePromise
{
return [FBLPromise async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
fulfill(@(20));
}];
}
ps:上面列举了
promise一些常见的方法,其中thendoallcatch的结合使用最为常见,async更常见于创建promise对象,掌握这几个基本就够项目中使用了,下面说一说promise中核心的两个方法
observeOnQueue:fulfill:reject
- (void)observeOnQueue:(dispatch_queue_t)queue
fulfill:(FBLPromiseOnFulfillBlock)onFulfill
reject:(FBLPromiseOnRejectBlock)onReject {
NSParameterAssert(queue);
NSParameterAssert(onFulfill);
NSParameterAssert(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的state,如果是FBLPromiseStateFulfilled,直接执行onFulfill回调,将value值回调出去。如果是FBLPromiseStateRejected,直接执行onReject回调,将error值回调出去。否则会检查_observers是否为空,为空则创建一个观察者数组,此数组中存放的对象是FBLPromiseObserver,它实际上是一个block块typedef void (^FBLPromiseObserver)(FBLPromiseState state, id __nullable resolution);是一个含有state和resolution参数的无返回值block,此数组中的block对象会延迟到最终的fulfill:和reject:方法中执行回调,在回到中根据state值,确定执行下一步操作,将对应的值返回出去。
chainOnQueue:chainedFulfill:chainedReject:
- (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;
}
这个方法的核心要点,也在observeOnQueue:上,方法中首先定义了一个pending状态的promise对象和resolver回调,然后自身执行的是observeOnQueue: 方法,在回调里,去执行resolver,那么resolver中做了什么呢?很简单,根据回传过来的值判断是否是promise类型,如果是,则对此promise添加观察者,否则直接执行fulfill方法,个人认为这里可以细化判断是否是NSError类型,从而更精确的执行reject:或者fulfill:,不过fulfill:接收参数本身就是id类型,且内部也做了NSError的过滤,直接执行也是可以的
只要理解了这两个方法,那么promise的核心要点也就理解了,最后做一个简单总结:
总结:简而言之,
promise的实现核心归于对block块的延迟处理,结合GCD以及链式调用的思想,从而可以用优雅简洁的方式来处理异步任务,如果想更深刻地理解,可以打断点一步步调试