RJIterator
生成器与迭代器的Objective-C实现,实现类似ES6的yield语意,ES7 async, await异步方案,支持在Objective-C/Swift项目中以同步风格编写异步代码,避免长回调链和Promise链.
Install / Use
/learn @renjinkui2719/RJIteratorREADME
生成器与迭代器是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(^{
//同步风格的代码
})
.finally(^{
//收尾,总会走这里
});
Swift:
rj_async {
//...
}
.finally {
//...
}
以登录举例
比如有这样的登录场景: 登录成功 --> 查询个人信息 --> 下载头像 --> 给头像加特效 --> 进入详情.
为了举例,假设要求每一步必须在上一步完成之后进行.
该功能可以使用异步块如下实现:
(1) 定义异步任务
//登录
- (RJAsyncClosure)loginWithAccount:(NSString *)account pwd:(NSString *)pwd {
//返回RJAsyncClosurele类型block
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 {
rj_async(^{
//每次await 的 result
RJResult *result = nil;
[ProgressHud show];
//开始登录...
result = rj_await( [self loginWithAccount:@"112233" pwd:@"12345"] );
if (result.error) {
//登录失败
return ;
}
//登录完成
NSDictionary *login_josn = result.value;
//开始拉取个人信息...
result = rj_await( [self queryInfoWithUid:login_josn[@"uid"] token:login_josn[@"token"]] );
if (result.error) {
//拉取个人信息失败
return ;
}
//拉取个人信息完成
NSDictionary *info_josn = result.value;
//开始下载头像...
result = rj_await( [self downloadHeadImage:info_josn[@"url"]] );
if (result.error) {
//下载头像失败
return ;
}
//下载头像完成
UIImage *head_image = result.value;
//开始处理头像
result = rj_await( [self makeEffect:head_image] );
if (result.error) {
//处理头像失败
return ;
}
//处理头像完成
head_image = result.value;
//全部完成,进入详情界面
UserInfoViewController *vc = [[UserInfoViewController alloc] init];
vc.uid = login_josn[@"uid"];
vc.token = login_josn[@"token"];
vc.name = info_josn[@"name"];
vc.headimg = head_image;
[self presentViewController:vc animated:YES completion:NULL];
})
.finally(^{
//收尾
[ProgressHud dismiss];
});
}
rj_async块内部完全以同步方式编写,通过把异步任务包装进rj_await(),rj_async会自动以异步方式调度它们,不会阻塞主流程,在主观感受上,它们是同步代码,功能逻辑也比较清晰.实际上,任何"等待异步回调 -> 下一步"类型的逻辑都可以转化成如上的写法.
rj_async块内部运行在主线程,可以直接在块内部进行UI操作.
rj_await可以理解为:"等待异步任务完成并返回结果",但是这种等待是不阻塞主线程的.
RJIterator兼容PromiseKit.如果已有自己的一个Promise,可以在异步块内直接传给rj_await(),它会被正确异步调度, 但是只支持AnyPromise,如果不是AnyPromise,如果可以转化的话,使用PromiseKit提供的相关方法转为AnyPromise再使用. 比如:
rj_async {
let fetchImage = URLSession.shared.dataTask(.promise, with: URL.init(string: "http://oem96wx6v.bkt.clouddn.com/bizhi-1030-1097-2xx.jpg")!).compactMap{ UIImage(data: $0.data) }
let result = rj_await( AnyPromise.init(fetchImage) )
if let error = result.error {
print("下载头像失败:\(error)")
return
}
let image = result.value as! UIImage
print("下载头像成功, image:\(image)")
}
下面是该登录功能举例对应的Swift写法:
//登录
func login(account: String, pwd: String) -> RJAsyncClosure {
//返回RJAsyncClosure类型闭包
return { (callback: @escaping RJAsyncCallback) in
//以asyncAfter 模拟Http请求 + 回调
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 2, execute: {
//登录成功
callback(["uid": "80022", "token":"67625235555"], nil);
})
};
}
//查询个人信息
func query(uid:String, token: String) -> RJAsyncClosure {
return { (callback: @escaping RJAsyncCallback) in
//以asyncAfter 模拟Http请求 + 回调
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 2, execute: {
//查询成功
callback(["name": "JimGreen", "url":"http://oem96wx6v.bkt.clouddn.com/bizhi-1030-1097-2.jpg"], NSError.init(domain: "s2", code: -1, userInfo: nil));
})
};
}
//下载头像
func download(url: String) -> RJAsyncClosure {
return {(callback: @escaping RJAsyncCallback) in
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 2, execute: {
do {
let data: Data? = try Data.init(contentsOf: URL.init(string: url)!)
let iamge = UIImage.init(data: data!)
//下载成功
callback(iamge, nil)
} catch let error {
//下载失败
callback(nil, error)
}
})
};
}
//处理头像
func makeEffect(image: UIImage) -> RJAsyncClosure {
return { (callback: @escaping RJAsyncCallback) in
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 2, execute: {
//处理成功
callback(image, nil);
})
};
}
@objc func onLogin(_ sender: Any? = nil) {
rj_async {
//每次await 的 result
var result: RJResult
ProgressHud.show()
//开始登录
result = rj_await( self.login(account: "112233", pwd: "445566") )
if let error = result.error {
//登录失败
return
}
//登录成功
let login_json = result.value as! [String: String]
//开始查询信息
result = rj_await( self.query(uid: login_json["uid"]!, token: login_json["token"]!) )
if let error = result.error {
//查询信息失败
return
}
//查询信息成功
let info_json = result.value as! [String: String]
//开始下载头像
result = rj_await( self.download(url: info_json["url"]!) )
if let error = result.error {
//下载头像失败
return
}
//下载头像成功
let image = result.value as! UIImage
//开始处理头像
result = rj_await( self.makeEffect(image: image) )
if let error = result.error {
//处理头像失败
return
}
//处理头像成功
let beautiful_image = result.value as! UIImage
//进入详情界面
}
.finally {
//登录收尾
ProgressHud.dismiss()
}
}
对比普通回调方式编写代码
如果以普通回调方式,则不论如何逃不出如下模式:
- (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
*/
}
}];
}
