生成器和迭代器是 ES6 和 Python 的关键概念,初体验后感受到它们的强大,特别是在异步调用中的运用。RJIterator 是该功能的 OC 实现,可以在 OC/Swift 项目中使用。
1. 异步的运用
异步任务
在 RJIterator 中,一个 RJAsyncClosure 类型的闭包就是一个异步任务,可以被异步调度。
RJAsyncClosure 是 RJIterator 定义的闭包类型:
typedef void (^RJAsyncCallback)(id _Nullable value, id _Nullable error);
typedef void (^RJAsyncClosure)(RJAsyncCallback _Nonnull callback);
同时,RJIterator 兼容 PromiseKit,RJIterator 在运行时会判断,如果一个对象是 AnyPromise 类型,它也是一个异步任务。
异步块
使用 rj_async 声明一个异步块,表示代码块内部将以异步方式调度执行
Objective-C
rj_async(^{
//异步代码
})
.error(^(id error) {
//出错处理
})
.finally(^{
//收尾 不论成功还是出错都会执行
});
Swift
rj_async {
//...
}
.error {error in
let error = error as! MyErrorType
//...
}
.finally {
//...
}
以登录为例
比如有如下登录场景:登录成功 --> 查询个人信息 --> 下载头像 --> 给头像加特效 --> 进入详情。
为了举例,假设要求每一步必须在上一部完成后才能进行。该功能可以使用异步块如下实现
(1) 定义异步任务
//登录
- (RJAsyncClosure)loginWithAccount:(NSString *)account pwd:(NSString *)pwd {
return ^(RJAsyncCallback callback){
//调用http接口
post(@"/login", account, pwd, ^(id response, error) {
callback(response.data, error);
});
};
}
//拉取信息
- (RJAsyncClosure)queryInfoWithUid:(NSString *)uid token:(NSString *)token{
return ^(RJAsyncCallback callback){
get(@"query", uid, token, ^(id response, error) {
callback(response.data, error);
});
};
}
//下载头像
- (RJAsyncClosure)downloadHeadImage:(NSString *)url{
return ^(RJAsyncCallback callback){
get(@"file", url, ^(id response, error) {
callback(response.data, error);
});
};
}
//处理头像
- (RJAsyncClosure)makeEffect:(UIImage *)image{
return ^(RJAsyncCallback callback){
make(image, ^(id data, error) {
callback(data, error);
});
};
}
(2)以同步方式编写代码
- (void)onLogin:(id)sender {
[ProgressHud show];
rj_async(^{
NSDictionary *login_josn = rj_yield( [self loginWithAccount:@"112233" pwd:@"12345"] );
NSDictionary *query_json = rj_yield( [self queryInfoWithUid:login_josn[@"uid"] token:login_josn[@"token"]] );
UIImage *image = rj_yield( [self downloadHeadImage:query_json[@"url"]] );
NSString *beautiful_image = rj_yield( [self makeEffect:image] );
NSLog(@"all done");
//进入详情界面
})
.error(^(id error) {
NSLog(@"error happened");
})
.finally(^{
[ProgressHud dismiss];
});
}
rj_async块内部完全以同步方式编写,通过将异步任务包装进rj_yield(),rj_async会自动以异步方式调度它们,不会阻塞主流程,从主观感受上,它们就像同步代码一样,功能逻辑也比较清晰。
rj_async块内部运行在主线程,可以直接在块内部进行UI操作。
这里async的含义并不是启动子线程来执行块,而是块内部以异步方式调度。异步意味着不阻塞,异步不一定就是子线程。
RJIterator兼容PromiseKit。如果已有自己的Promise,可以在异步块内直接传给rj_yield(),它会被正确异步调度,但是只支持AnyPromise。如果不是AnyPromise,如果可以转换的话,使用PromiseKit提供的相关方法转换为AnyPromise后再使用。
对比普通回调方式编写代码
如果以普通回调方式,则无论怎样都无法逃出以下模式:
- (void)loginWithAccount:(NSString *)account pwd:(NSString *)pwd callback:(void (^)(id value, id error))callback {
post(@"/login", account, pwd, ^(id response, error) {
callback(response.data, error);
});
}
- (void)queryInfoWithUid:(NSString *)uid token:(NSString *)token callback:(void (^)(id value, id error))callback{
get(@"query", uid, token, ^(id response, error) {
callback(response.data, error);
});
}
- (void)downloadHeadImage:(NSString *)url callback:(void (^)(id value, id error))callback{
get(@"file", url, ^(id response, error) {
callback(response.data, error);
});
}
- (void)makeEffect:(UIImage *)image callback:(void (^)(id value, id error))callback{
make(image, ^(id data, error) {
callback(data, error);
});
}
- (void)onLogin:(id)sender {
[ProgressHud show];
[self loginWithAccount:@"112233" pwd:@"112345" callback:^(id value, id error) {
if (error) {
[ProgressHud dismiss];
NSLog(@"Error happened:%@", error);
}
else {
NSDictionary *json = (NSDictionary *)value;
[self queryInfoWithUid:json[@"uid"] token:json[@"token"] callback:^(id value, id error) {
if (error) {
[ProgressHud dismiss];
NSLog(@"Error happened:%@", error);
}
else {
NSDictionary *json = (NSDictionary *)value;
[self downloadHeadImage:json[@"url"] callback:^(id value, id error) {
if (error) {
[ProgressHud dismiss];
NSLog(@"Error happened:%@", error);
}
else {
UIImage *image = (UIImage *)value;
[self makeEffect:image callback:^(id value, id error) {
if (error) {
[ProgressHud dismiss];
NSLog(@"Error happened:%@", error);
}
else {
[ProgressHud dismiss];
UIImage *image = (UIImage *)value;
/*
All done
*/
}
}];
}
}];
}
}];
}
}];
}
这时onLogin方法就掉进了传说中的回调地狱。
对比Promise链
[ProgressHud show];
[self loginWithAccount:@"112233" pwd:@"12345"].promise
.then(^(NSDictionary *json) {
return [self queryInfoWithUid:json[@"uid"] token:json[@"token"]].promise;
})
.then(^(NSDictionary *json) {
return [self downloadHeadImage:json[@"url"]].promise;
})
.then(^(UIImage *image) {
return [self makeEffect:image].promise;
})
.then(^(UIImage *image) {
/*All done*/
})
.catch(^(id error) {
NSLog(@"error happened");
})
.finally(^{
[ProgressHud dismiss];
});
2.生成器与迭代器
生成器与迭代器的概念及用法。可以参考ES6教程
http://www.infoq.com/cn/articles/es6-in-depth-generators
http://es6.ruanyifeng.com/#docs/generator
在RJIterator中,满足以下条件的C/Objective-C/Swift方法的闭包即可以作为生成器
(1)返回值为id或void,接受最多8个id参数的OC类方法、实例方法、block;c函数;Swift类方法、实例方法。
(2)返回值为id,接受一个参数的Swift函数、闭包。
生成器不能直接调用,需要通过RJIterator类的初始化方法创建迭代器,再通过迭代器访问生成器。
- (id _Nonnull)initWithFunc:(RJGenetarorFunc _Nonnull)func arg:(id _Nullable)arg;
- (id _Nonnull)initWithTarget:(id _Nonnull)target selector:(SEL _Nonnull)selector, ...;
- (id _Nonnull)initWithBlock:(id _Nonnull)block, ...;
- (id _Nonnull)initWithTarget:(id _Nonnull)target selector:(SEL _Nonnull)selector args:(NSArray *_Nullable)args;
- (id _Nonnull)initWithBlock:(id _Nullable (^ _Nonnull)(id _Nullable))block arg:(id _Nullable)arg;
- (id _Nonnull)initWithStandardBlock:(dispatch_block_t _Nonnull)block;
低配版聊天机器人
假设talk是一个会说话的机器人,按一下它就会回一句。那么talk可以这样实现:
func talk(arg: Any?) -> Any? {
rj_yield("Hello, How are you?");
rj_yield("Today is Friday");
rj_yield("So yestday is Thursday");
rj_yield("And tomorrow is Saturday");
rj_yield("Over");
return "==talk done==";
}
在这个例子中,talk是一个生成器。每次调用都会返回“下一句话”。它会记住上一次说到哪里。调用的方式必须通过迭代器进行,因此首先创建谈的迭代器,然后通过next方法依次获取回答。
var it: RJIterator;
var r: RJResult;
it = RJIterator.init(withFunc: talk, arg: nil)
r = it.next()
print("value: \(r.value), done:\(r.done)")
//==> value: Hello How are you?, done:NO
r = it.next()
print("value: \(r.value), done:\(r.done)")
//==> value: Today is Friday, done:NO
r = it.next()
print("value: \(r.value), done:\(r.done)")
//==> value: So yestday is Thursday, done:NO
r = it.next()
print("value: \(r.value), done:\(r.done)")
//==> value: And tomorrow is Saturday, done:NO
r = it.next()
print("value: \(r.value), done:\(r.done)")
//==> value: Over, done:NO
r = it.next()
print("value: \(r.value), done:\(r.done)")
//==> value: ==talk done==, done:YES
r = it.next()
print("value: \(r.value), done:\(r.done)")
//==> value: ==talk done==, done:YES
RJResult是迭代器RJIterator每次next返回的结果值的类型,其中value表示结果数据,done表示迭代是否结束,结束表示生成器内部已经执行了尾部的或者在某些地方返回了。
每次调用next,talk都会从rj_yield处返回,这可以看作是暂时中断talk,等到再次执行next时,talk会从上次中断的地方恢复继续执行。这种“切换”方式类似于协程,但是RJIterator不是一个完整的协程库。协程库大部分目的是提升服务端的性能,因此有效的协程调度非常重要,而RJIterator的核心在于实现yield原语和async块。
新的需求
感觉还不够好,比如想让机器人知道我的名字,以增进我们之间的感情。修改talk
func talk(name: Any?) -> Any? {
rj_yield("Hello \(name), How are you?");
rj_yield("Today is Friday");
rj_yield("So yestday is Thursday");
rj_yield("And tomorrow is Saturday");
rj_yield("Over");
return "==talk done==";
}
并在创建迭代器时传递参数。
it = RJIterator.init(withFunc: talk, arg: "乌卡卡")
这时候第一次调用next,将如下返回
value: Hello 乌卡卡, How are you?, done:NO
更新的需求
在第5次调用next和talk对话时,它回答“Over”,然后再次迭代(第6次)就会结束。但如果还想继续聊天,可以在第6次迭代时,传递命令告诉机器人再次开始。
修改talk
fileprivate func talk(name: Any?) -> Any? {
var cmd = ""
repeat {
rj_yield("Hello \(name ?? ""), How are you?");
rj_yield("Today is Friday");
rj_yield("So yestday is Thursday");
rj_yield("And tomorrow is Saturday");
cmd = rj_yield("Over") as? String ?? "";
}while cmd != "Over"
return "==talk done==";
}
第6次调用next时传递值
r = it.next("again")
print("value: \(r.value), done:\(r.done)")
//value: value: Hello 乌卡卡, How are you?, done:NO
如果不传递这个值,talk将按正常流程结束。但是传入“again”后,它将从头开始。也就是说,生成器talk除了具有“中断+返回”的能力外,还拥有多次“传入值”的能力。其中原理是
给next传的值将作为生成器内部上次rj_yield的新返回值,并在生成器“苏醒”的时候赋值给左边的"cmd"。如果next未传递参数,则该返回值就是rj_yield()本身封装的值。通过这个特性,可以基于生成器和迭代器变种出新功能,rj_async块就是基于这个原理。
生成器嵌套
生成器内部可以嵌套调用其他生成器,例如要从1数到9,感觉工作量太大,所以定义了3个生成器,每个负责数3个数,再定义一个总生成器,内部调用这三个小生成器。
func count_1_3(_: Any?) -> Any? {
rj_yield(1)
rj_yield(2)
return 3
}
func count_4_5(_: Any?) -> Any? {
rj_yield(4)
rj_yield(5)
return 6
}
func count_7_9(_: Any?) -> Any? {
rj_yield(7)
rj_yield(8)
return 9
}
func count(_: Any?) -> Any? {
rj_yield(RJIterator.init(withFunc: count_1_3, arg: nil))
rj_yield(RJIterator.init(withFunc: count_4_5, arg: nil))
rj_yield(RJIterator.init(withFunc: count_7_9, arg: nil))
return nil
}
为count创建迭代器,连续执行next将得到value: 1, 2, 3, 4, 5, 6, 7, 8, 9
可能
苹果文档透露Swift以后的版本可能会新增异步和多任务方面的新特性,因此以后的Swift可能会像JavaScript一样支持async、yield、await等功能。
安装
pod
pod "RJIterator", "~> 1.1.3"
手动
RJIterator基于MRC管理内存,包含一个Swift文件,因此还要手动添加并更改配置,新增Bridge Header,比较麻烦,建议使用pod。