iOS开发中填坑工具暂存promise(异步响应式编程-解决地狱回调)

Objective-C 之 PromiseKit入门

2019-05-17  本文已影响0人  我唔知啊
图片来源:https://github.com/mxcl/PromiseKit

一、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种状态:pendingresolved(本人习惯翻译为:等待处理和处理结束),
其中resolved包括fulfilled(处理成功)和rejected(处理失败)。

为什么上段代码中adapter([NSDate date], nil);代表处理成功,而adapter(nil, error);代表处理失败呢?

接下来了解PMKAdapter

PMKAdapter可以传递2个参数,第一个是id类型的任何对象,可以是nil,第二个是NSError对象,也可以是nil。传递不同参数,任务的状态会不同,如下图所示:

传递不同参数,AnyPromise的状态不一样

因此可以总结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

  1. 下载项目

  2. 把PromiseKit项目放在你的项目文件夹,如图:

文件夹位置
  1. 然后把PromiseKit.xcodeproj文件拖到你的项目
项目位置
  1. 前往Target-Embbedded Binaries添加PromiseKit.framework,如图:
导入Framework
  1. 导入头文件,编译
#import <PromiseKit/PromiseKit.h>

此时可能报错:

编译报错

解决方法:把Build Active Architecture Only属性改为YES

修改属性

这样,就能在模拟器上使用PromiseKit。但是依然不能在真机上运行,可能报错:

运行报错

解决方法:修改PromiseKit的可用架构

修改可用架构

报错原因:由于在OC中使用了Swift,编译成功后Xcode会生成PromiseKit-Swift.h文件。如果编译失败,则没有此文件,因此报错。

到此,成功集成PromiseKit。

参考:

https://juejin.im/post/5c78eac2f265da2db4143ca2

https://juejin.im/post/5c1649cd518825697324352c

http://www.hangge.com/blog/cache/detail_2231.html

上一篇下一篇

猜你喜欢

热点阅读