PromiseKit,优雅的的管理多个异步操做,让你今后远离多层嵌套。html
用一个例子做比较:git
需求:先注册,后登陆,提示登陆成功。github
// 常规方法:嵌套执行多个任务
[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实现的方法更美观,且更易维护。算法
仍是上面那个需求,下面是具体实现步骤:数组
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);
});
复制代码
接下来逐句解读该段代码,并穿插知识点。xcode
AnyPromise
能够看做是任务
,下文以任务
代替。promise
- (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 ;
复制代码
任务
的初始化方法中附有一个Block:PMKAdapter
,其返回的参数表达了该任务
的处理结果是成功仍是失败,如:bash
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) ;
复制代码
signUpPromise.then(^{// 注册成功走这里
NSLog(@"注册成功");
return signInPromise;// 返回signInPromise:接下来执行登陆操做
}).then(^(NSDate* date){// 登陆成功走这里
NSLog(@"登陆成功, 时间:%@",date);
}).catch(^(NSError* error){// 注册失败或者登陆失败跳到这里
NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey);
});
复制代码
若是
任务
处理成功
,就会执行紧接其后的then
方法;若是处理失败
,则会跳事后面的全部then
方法,执行catch
方法。
第一:若是任务
处理成功,
能够在then
方法里面手动
增长最多3个参数:
.then(^(NSDate* date, ..., ...){
NSLog(@"date: %@",date);
})
复制代码
第二:若是任务
处理失败
,能够把参数传给NSError
:
adapter(nil,[self errorWithMessage:"登陆失败!"]);
复制代码
而后在catch
方法提取:
.catch(^(NSError* error){
NSLog(@"%@",error.userInfo[NSLocalizedDescriptionKey);
})
复制代码
AnyPromise是PromiseKit的关键类,一个AnyPromise对象能够看做是一个任务。
@property (nonatomic, readonly) __nullable id value; // 已经处理的Promise的值
@property (nonatomic, readonly) BOOL pending; // 等待处理
@property (nonatomic, readonly) BOOL fulfilled; // 处理成功
@property (nonatomic, readonly) BOOL rejected; // 处理失败
复制代码
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;
复制代码
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;
复制代码
ensure
:当任务
处理结束
,在主线程执行。
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_block_t __nonnull))ensure;
复制代码
ensureOn
:当任务
处理结束
,在指定线程执行。
- (AnyPromise * __nonnull(^ __nonnull)(dispatch_queue_t __nonnull, dispatch_block_t __nonnull))ensureOn;
复制代码
全部链接词在指定线程执行的用法相似:
.ensureOn(queue, ^{
})
复制代码
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);
}
}
});
复制代码
extern AnyPromise * __nonnull PMKWhen(id __nonnull input);
复制代码
等待全部任务
处理成功
,执行下一步。(一旦有任务
处理失败
,当即跳转到thencatch
方法,且中止执行其余任务)
官方注释:
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]);
});
复制代码
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]);
});
复制代码
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
});
复制代码
extern id __nullable PMKHang(AnyPromise * __nonnull promise);
复制代码
阻塞线程,直到任务
处理结束
。这个作法不安全,只在调试时用!!!
例子:阻塞当前线程,直到注册成功或者注册失败才会执行next step。
id value = PMKHang([self promise_signUpWithUserName:uid pwd:pwd]);
NSLog(@"%@ is resolved",value);
// next step
// ...
复制代码
设定任务执行的超时时间
// 限定注册任务的超时时间为2秒
PMKRace(@[PMKWhen([HTTPUtil promise_signUpWithUserName:uid pwd:pwd]), PMKAfter(2.).then(^{
NSLog(@"计时结束~");
})]).then(^(NSString* msg){
NSLog(@"%@",msg);
});
复制代码
刚着手把PromiseKit集成到项目的时候,按照网上的教程,不管是CocoaPods仍是手动安装都失败,通过摸索,步骤以下:
版本:Xcode 10.1,PromiseKit 6.8.4
把PromiseKit项目放在你的项目文件夹,如图:
#import <PromiseKit/PromiseKit.h>
复制代码
此时可能报错:
解决方法:把Build Active Architecture Only属性改成YES
这样,就能在模拟器上使用PromiseKit。可是依然不能在真机上运行,可能报错:
解决方法:修改PromiseKit的可用架构
到此,成功集成PromiseKit。
参考: