一、PromiseKit介绍
PromiseKit,优雅的的管理多个异步操作,让你从此远离多层嵌套。
用一个例子作比较:
需求:先注册,后登录,提示登录成功。
// 常规方法:嵌套执行多个任务[self signUpWithUserName:uid pwd:pwd resultBlock:^(BOOL success, NSString *message) { if (success) { [self signInWithUserName:uid pwd:pwd resultBlock:^(BOOL success, NSString *message) { if (success) { NSLog(@"登录成功"); } }]; }}];复制代码
// PromiseKit提供的方法,简洁清晰的展示多任务操作signUpPromise.then(^{ return signInPromise;}).then(^{ NSLog(@"登录成功");}).catch(^(NSError* error){});复制代码
由此可见,用PromiseKit实现的方法更美观,且更易维护。
二、PromiseKit的使用
还是上面那个需求,下面是具体实现步骤:
1. 修改现有的注册任务和登录任务,如下:// 注册伪代码(登录代码类似)- (AnyPromise*)promise_signUpWithUserName:(NSString*)uid pwd:(NSString*)pwd { return [AnyPromise promiseWithAdapterBlock:^(PMKAdapter _Nonnull adapter) { // 网络请求 xxx if(注册成功) { adapter(@"注册成功", nil); }else { adapter(nil,[self errorWithMessage:"注册失败!"]); // error自己定义 } }];}- (NSError*)errorWithMessage:(NSString*)msg { return [[NSError alloc] initWithDomain:NSNetServicesErrorDomain code:-101 userInfo:@{NSLocalizedDescriptionKey:msg}];}2. 在一个地方管理所有异步操作[HTTPUtil promise_signUpWithUserName:uid pwd:pwd].then(^{// 注册成功走这里 NSLog(@"注册成功"); return [HTTPUtil promise_signInWithUserName:uid pwd:pwd];// 接下来执行登录操作}).then(^(NSDate* date){// 登录成功走这里 NSLog(@"登录成功, 时间:%@",date);}).catch(^(NSError* error){// 注册失败或者登录失败跳到这里,不会执行then方法 NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey);});复制代码
接下来逐句解读该段代码,并穿插知识点。
2.1 创建任务
AnyPromise
可以看作是任务
,下文以任务
代替。
- (AnyPromise*)promise_signUpWithUserName:(NSString*)uid pwd:(NSString*)pwd { return [AnyPromise promiseWithAdapterBlock:^(PMKAdapter _Nonnull adapter) { }];}复制代码
任务
的所有初始化方法如下,可在源码查看它们说明,这里不一一解释:
// 常用+ (instancetype __nonnull)promiseWithAdapterBlock:(void (^ __nonnull)(PMKAdapter __nonnull adapter))block; + (instancetype __nonnull)promiseWithResolverBlock:(void (^ __nonnull)(__nonnull PMKResolver))resolveBlock; + (instancetype __nonnull)promiseWithValue:(__nullable id)value;+ (instancetype __nonnull)promiseWithIntegerAdapterBlock:(void (^ __nonnull)(PMKIntegerAdapter __nonnull adapter))block;+ (instancetype __nonnull)promiseWithBooleanAdapterBlock:(void (^ __nonnull)(PMKBooleanAdapter __nonnull adapter))block;- (instancetype __nonnull)initWithResolver:(PMKResolver __strong __nonnull * __nonnull)resolver ;复制代码
2.2 处理Block
任务
的初始化方法中附有一个Block:PMKAdapter
,其返回的参数表达了该任务
的处理结果是成功还是失败,如:
if(登录成功) { adapter([NSDate date], nil);// 处理成功}else { adapter(nil, error); // 处理失败}复制代码
任务
有2种状态:pending
和resolved
(本人习惯翻译为:等待处理和处理结束), 其中resolved
包括fulfilled
(处理成功)和rejected
(处理失败)。
为什么上段代码中adapter([NSDate date], nil);
代表处理成功,而adapter(nil, error);
代表处理失败呢?
接下来了解PMKAdapter
:
PMKAdapter
可以传递2个参数,第一个是id类型的任何对象,可以是nil,第二个是NSError
对象,也可以是nil。传递不同参数,任务
的状态会不同,如下图所示:
因此可以总结PMKAdapter
的参数:
总结一:如果任意一个参数的类型是
NSError
,任务
处理失败
;
总结二:2个参数都是nil或者第一个是id,第二个是nil,
任务
处理成功
。
处理结果将影响第三步的操作。
下面是所有Block的定义:
typedef void (^PMKAdapter)(id __nullable, NSError * __nullable) ;typedef void (^PMKResolver)(id __nullable);typedef void (^PMKIntegerAdapter)(NSInteger, NSError * __nullable) ;typedef void (^PMKBooleanAdapter)(BOOL, NSError * __nullable) ;复制代码
2.3 在一个地方管理所有异步操作
signUpPromise.then(^{// 注册成功走这里 NSLog(@"注册成功"); return signInPromise;// 返回signInPromise:接下来执行登录操作}).then(^(NSDate* date){// 登录成功走这里 NSLog(@"登录成功, 时间:%@",date);}).catch(^(NSError* error){// 注册失败或者登录失败跳到这里 NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey);});复制代码
如果
任务
处理成功
,就会执行紧接其后的then
方法;如果处理失败
,则会跳过后面的所有then
方法,执行catch
方法。
怎样才能使用Block传递过来的参数呢?
第一:如果任务
处理成功,
可以在then
方法里面手动
增加最多3个参数:
.then(^(NSDate* date, ..., ...){ NSLog(@"date: %@",date);})复制代码
第二:如果任务
处理失败
,可以把参数传给NSError
:
adapter(nil,[self errorWithMessage:"登录失败!"]);复制代码
然后在catch
方法提取:
.catch(^(NSError* error){ NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey);})复制代码
三、PromiseKit之AnyPromise
AnyPromise是PromiseKit的关键类,一个AnyPromise对象可以看作是一个任务。
1 属性
@property (nonatomic, readonly) __nullable id value; // 已经处理的Promise的值@property (nonatomic, readonly) BOOL pending; // 等待处理@property (nonatomic, readonly) BOOL fulfilled; // 处理成功@property (nonatomic, readonly) BOOL rejected; // 处理失败复制代码
2 连接词
2.1 then
then
:当任务
处理成功
,在主线程执行。
- (AnyPromise * __nonnull (^ __nonnull)(id __nonnull))then;复制代码
thenInBackground
:当任务
处理成功
,在默认线程执行。
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))thenInBackground;复制代码
thenOn
:当任务
处理成功
,在指定线程执行。
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, id __nonnull))thenOn;复制代码
2.2 catch
catch
:当任务
处理失败
,在主线程运行。
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catch;复制代码
catchInBackground
:当任务
处理失败
,在global线程
执行。
- (AnyPromise * __nonnull(^ __nonnull)(id __nonnull))catchInBackground;复制代码
catchOn
:当任务
处理失败
,在指定线程执行。
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, id __nonnull))catchOn;复制代码
2.3 ensure
ensure
:当任务
处理结束
,在主线程执行。
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))ensure;复制代码
ensureOn
:当任务
处理结束
,在指定线程执行。
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, dispatch_block_t __nonnull))ensureOn;复制代码
所有连接词在指定线程执行的用法类似:
.ensureOn(queue, ^{})复制代码
3 进阶用法
3.1 PMKJoin
AnyPromise *__nonnull PMKJoin(NSArray * __nonnull promises);复制代码
等待所有任务
处理结束
,执行下一步。(所有任务
处理结束
才会处理处理失败
,如果有就跳转到then
方法)
官方注释:
PMKJoin
waits on all provided promises, then rejects if any of those promises rejects, otherwise it fulfills with values from the provided promises.
大意:
PMKJoin
将会等待所有任务
(promises
参数提供)处理结束
,之后如果有一个任务
处理失败
,PMKJoin
才会处理失败
,否则处理成功
。
例子:吃完饭,喝完汤,才能吃水果。
PMKJoin(@[ricePromise, soupPromise]).then(^(NSArray* messages){// 此时参数是数组类型 return fruitPromise;}).catch(^(NSError* error){ NSArray* promises = error.userInfo[PMKJoinPromisesKey];// 错误信息中包含了所有任务 for (AnyPromise* promise in promises) { if (promise.rejected) { NSLog(@"%@ is rejected",promise); } }});复制代码
3.2 PMKWhen
extern AnyPromise * __nonnull PMKWhen(id __nonnull input);复制代码
等待所有任务
处理成功
,执行下一步。(一旦有任务
处理失败
,立即跳转到then
方法,且停止执行其他任务)
官方注释:
PMKWhen
rejects as soon as one of the provided promises rejects.
大意:只要其中一个
任务
(input
参数提供)处理失败
,PMKWhen
立即处理失败
。
例子:做饭,拿碗筷,才能吃饭。
PMKWhen(@[cookPromise, setPromise]).then(^(NSArray* messages){// 此时参数是数组类型 return eatPromise;}).catch(^(NSError* error) {// "处理失败"的任务 NSInteger index = [error.userInfo[PMKFailingPromiseIndexKey] integerValue]; NSLog(@"index:%@ error: %@\n", @(index),error.userInfo[NSLocalizedDescriptionKey]);});复制代码
3.3 PMKRace
extern AnyPromise * __nonnull PMKRace(NSArray * __nonnull promises);复制代码
只要处理结束
一个任务
,立即执行then
或者catch
方法,其他任务
继续执行。(只关心第一个处理结束
的任务
,不关心其他任务
)
例子:在众多算法中找出解题速度最快的那个。
PMKRace(@[algPromise1, algPromise2, algPromise3]).then(^(id message){ NSLog(@"%@",message);}).catch(^(NSError* error){ NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey]);});复制代码
3.4 PMKAfter
extern AnyPromise * __nonnull PMKAfter(NSTimeInterval duration);复制代码
延迟一定时间执行任务
,相当于dispatch_after
。
例子:注册成功后3秒再登录。
signUpPromise.then(^{ // 注册成功 return PMKAfter(3.f).then(^{// 延迟执行signInPromise return signInPromise; });}).then(^{ // 登录成功}).catch(^(NSError* error){ // handle error});复制代码
3.5 PMKHang
extern id __nullable PMKHang(AnyPromise * __nonnull promise);复制代码
阻塞线程,直到任务
处理结束
。这个做法不安全,只在调试时用!!!
例子:阻塞当前线程,直到注册成功或者注册失败才会执行next step。
id value = PMKHang([self promise_signUpWithUserName:uid pwd:pwd]);NSLog(@"%@ is resolved",value);// next step// ...复制代码
4. 综合使用
设定任务执行的超时时间
// 限定注册任务的超时时间为2秒PMKRace(@[PMKWhen([HTTPUtil promise_signUpWithUserName:uid pwd:pwd]), PMKAfter(2.).then(^{ NSLog(@"计时结束~");})]).then(^(NSString* msg){ NSLog(@"%@",msg);});复制代码
四、PromiseKit的集成(手动)
刚着手把PromiseKit集成到项目的时候,按照网上的教程,无论是CocoaPods还是手动安装都失败,经过摸索,步骤如下:
版本:Xcode 10.1,PromiseKit 6.8.4
-
把PromiseKit项目放在你的项目文件夹,如图:
- 然后把PromiseKit.xcodeproj文件拖到你的项目
- 前往Target-Embbedded Binaries添加PromiseKit.framework,如图:
- 导入头文件,编译
#import复制代码
此时可能报错:
解决方法:把Build Active Architecture Only属性改为YES
这样,就能在模拟器上使用PromiseKit。但是依然不能在真机上运行,可能报错:
解决方法:修改PromiseKit的可用架构
到此,成功集成PromiseKit。
参考: