yield
- 协程里面重要的关键字
yield
,在生成的迭代器调用next,每次都会将yield
后面的数据返回,并中断生成器。同时next后面可以传值,将会填充到yield
字段。
我们接下来以JS的代码为例:
//生成器
function * generator(){
var name = yield "蔡文姬";
console.log(name)
var age = yield 18;
console.log(age);
}
//迭代器
var iterator = generator();
iterator.next();
//{value: "蔡文姬", done: false}
iterator.next("小明")
//小明
//{value: 18, done: false}
iterator.next("10")
// 10
//{value: undefined, done: true}
- 可以看出生成器就是普通函数前面添加
*
号,迭代器由生成器的调用生成。 - 当迭代器调用next时,返回的是一个对象,key分别是
value
、done
来表示返回值以及是否迭代完毕。 - 可以看出第一调用next,返回数据是第一个yield后面的蔡文姬。第二次调用next时传入值
小明
,这样就把值传入生成器中。相当于替换了yield的位置,就是var name = "小明"
。当迭代器遍历完毕之后,返回的value值为空,done返回为true.
总结:yield修饰符具有中断性,将yield后面的数据返回,同时具有恢复现场的特性,再次进入可以携带数据,同时返回下一个yield后面数据,当迭代完毕,返回一个value为空的数据。done被置为true.
那么利用这个特性,可以实现异步流程同步化。
思路:迭代器的next我们可以理解为一次请求,我们在当前请求的回调里实现迭代器的下一个next,这样异步流程就能实现顺序执行。
1.首先我们一个参数是回调的方法,这样在异步耗时操作之后可以调用这个回调,进而进行下一次的next调用。**
//模拟网络请求
function mockReq(index,time){
return (callBack=>{
setTimeout(() => {
callBack(index);
}, time);
})
}
2.然后我们需要把多个异步请求的流程写下来(其实就是生成器),比如1,2,3,4四个请求顺序执行,
//生成器(异步请求,同步顺序执行)
function * generator(){
yield mockReq(1,3000);
yield mockReq(2,1000);
yield mockReq(3,3000);
yield mockReq(4,2000);
}
3.有了生成器步骤,那么接下来我们就可以用迭代器next来实现顺序执行:
function execute(generator){
//生成迭代器
var iterator = generator();
function nextStep() {
//每次返回的都是mockReq的返回值:一个带有回调的匿名函数。
var result = iterator.next();
//迭代器未迭代完毕
if(typeof result.value == "function"){
//执行异步请求,然后等待回调,递归调用nextStep,开始下个异步请求。
result.value(data=>{
console.log(data);
nextStep();
})
}else{
return;
}
}
//执行第一次的异步请求。
nextStep();
}
4.调用:
function start(){
execute(generator);
}
直接上源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
//模拟网络请求
function mockReq(index,time){
return (callBack=>{
setTimeout(() => {
callBack(index);
}, time);
})
}
//生成器(异步请求,同步顺序执行)
function * generator(){
yield mockReq(1,3000);
yield mockReq(2,1000);
yield mockReq(3,3000);
yield mockReq(4,2000);
}
function execute(generator){
var iterator = generator();
function nextStep() {
//每次返回的都是mockReq的返回值:一个带有回调的匿名函数。
var result = iterator.next();
//迭代器未迭代完毕
if(typeof result.value == "function"){
//执行异步请求,然后等待回调,递归调用nextStep
result.value(data=>{
console.log(data);
nextStep();
})
}else{
return;
}
}
//执行第一次的异步请求。
nextStep();
}
function start(){
execute(generator);
}
</script>
</head>
<body>
<input type="button" value="开始" onclick="start()">
</body>
</html>
控制台日志输出
可以看出 四个模拟的网络请求请求时间不同,但是执行顺序按照了我们规定的顺序执行。
至于后面我们常用的async/await用法其实跟这个差不多。只不过是经过了一层包装,让外部行为看起来比较统一。经过async修饰过方法会被包装成promise对象,这跟我们的上面的mockReq
其实大同小异,然后再网络请求的回调里执行resolve/ reject(其实就是上面的callBack).当然promise功能还有很多,我们只是讨论模仿async/await相关问题。
经过上面的描述,我们已经知道js的协程相关特性,但是iOS没有协程相关的方法,没有yield,没有async /await.我们想一下,yield主要作用是什么呢?中断
+恢复
现场的功能。虽然iOS没有这些,但是想一下GCD里面的信号量semaphore也具有中断特性(semaphore的数量为0,执行wait(Forever)就会中断当前线程),我们先开启一个异步任务,然后执行多个任务的主队列使用信号量进行中断,然后在单个的异步任务队列的回调里重新使信号量加一,从而恢复主任务队列,然后执行下一个子任务,依次类推。
那思路已经有了,其实就是把js里的yield换成了semaphore.涉及到任务队列,信号量,异步任务,我们可以用一个对象来关联他们,保证任务队列和异步任务里的信号量是同一个,但是对象在方法栈里生成,在方法栈结束时会被释放,这时异步任务可能还没有回来,此时就需要一个全局的dic来持有,通过barrier监听主任务队列,等到任务队列里的所有任务都执行完毕之后,将这个管理对象释放掉:
talk is cheap,show me the code
//JT_AsyncOperation.h 文件
@class JT_AsyncOperation,JT_AwaitResult;
typedef void(^JTRespClourse) (id response,id error);
typedef void(^JTWaitClosure)(JTRespClourse respClosure);
typedef void(^JTOperationBlock)(JT_AsyncOperation* operator);
#define JT_AWAIT(closure) _Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wundeclared-selector\"") \
[operator performSelector:@selector(await:) withObject:closure];\
_Pragma("clang diagnostic pop") \
@interface JT_AsyncOperation : NSObject
//异步任务同步顺序执行(返回对象是为了进行类似链式调用)
+ (JT_AsyncOperation *)async:(JTOperationBlock)operationBlock;
@end
//以后可对该类进行扩展或者继承
@interface JT_AwaitResult : NSObject
@property(nonatomic,strong)id value;
@property(nonatomic,strong)NSError * error;
@end
//JT_AsyncOperation.m 文件
#import "JT_AsyncOperation.h"
//存储全部的任务队列,防止栈结束JT_AsyncOperation被提前释放
static NSMutableDictionary * GlobalAsyncWaitQueues;
@interface JT_AsyncOperation()
//队列
@property (nonatomic, strong) dispatch_queue_t operationQueue;
//执行的任务
@property (nonatomic, strong) JTOperationBlock operationBlock;
//界限任务
@property (nonatomic, strong) JTOperationBlock barrierBlock;
//释放GlobalAsyncWaitQueues里的自身
@property (nonatomic, strong) JTOperationBlock freeBlock;
//信号量
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@end
@implementation JT_AsyncOperation
//构建基本信息
- (void)configWithOperation:(JTOperationBlock)operationBlock{
NSString * queueName = [NSString stringWithFormat:@"%p",self];
GlobalAsyncWaitQueues[queueName] = self;
self.operationQueue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_CONCURRENT);
self.operationBlock = operationBlock;
self.semaphore = dispatch_semaphore_create(0);
}
//执行任务(任务组1)
- (void)start{
__weak typeof(self) weakSelf = self;
dispatch_async(self.operationQueue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
!strongSelf.operationBlock?:strongSelf.operationBlock(strongSelf);
});
}
//barrier拦截(任务组2)
- (void)barrier{
__weak typeof(self) weakSelf = self;
dispatch_barrier_async(self.operationQueue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
!strongSelf.barrierBlock?:strongSelf.barrierBlock(strongSelf);
});
}
//finish Hook任务组3
- (void)free{
__weak typeof(self) weakSelf = self;
dispatch_async(self.operationQueue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
!strongSelf.freeBlock?:strongSelf.freeBlock(strongSelf);
});
}
//等待任务执行完毕只有返回
- (JT_AwaitResult *)await:(JTWaitClosure)closure{
__block JT_AwaitResult * result = [JT_AwaitResult new];
if(!closure) return nil;
closure(^(id response,id error){
result.value = response;
result.error = error;
dispatch_semaphore_signal(self.semaphore);
});
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
return result;
}
//异步任务同步顺序执行
+ (JT_AsyncOperation *)async:(JTOperationBlock)operationBlock{
if(!GlobalAsyncWaitQueues) GlobalAsyncWaitQueues = [NSMutableDictionary new];
JT_AsyncOperation * asyncOperation = [JT_AsyncOperation new];
[asyncOperation configWithOperation:operationBlock];
[asyncOperation start];
[asyncOperation freeObjWhenFinish];
return asyncOperation;
}
//进行barrier拦截
- (JT_AsyncOperation *)barrier:(JTOperationBlock)barrierBlock{
self.barrierBlock = barrierBlock;
[self barrier];
return self;
}
//finish之后free对象,防止内存泄漏
- (void)freeObjWhenFinish{
//需要barrier拦截,才能起到分隔作用
if(!self.barrierBlock){
//添加一个空的block,来进行分隔
[self barrier:^(JT_AsyncOperation *operator) {
}];
}
__weak typeof(self) weakSelf = self;
self.freeBlock = ^(JT_AsyncOperation * operator){
__strong typeof(weakSelf) strongSelf = weakSelf;
NSString * queueName = [NSString stringWithFormat:@"%p",strongSelf];
GlobalAsyncWaitQueues[queueName] = nil;
};
[self free];
}
- (void)dealloc{
// NSLog(@"JT_AsyncOperation被释放了");
}
@end
@implementation JT_AwaitResult
@end
用法
//模拟请求百度
JTWaitClosure RequestBaidu(){
return ^(JTRespClourse respClosure){
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
NSURLSessionTask * task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
!respClosure?:respClosure(@"BAIDU",error);
}];
[task resume];
};
}
//模拟请求bing
JTWaitClosure RequestBing(){
return ^(JTRespClourse respClosure){
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://cn.bing.com/"]];
NSURLSessionTask * task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
!respClosure?:respClosure(@"BING",error);
}];
[task resume];
};
}
//任务队列
void taskQueue(){
[JT_AsyncOperation async:^(JT_AsyncOperation *operator) {
JT_AwaitResult *result1 = JT_AWAIT(RequestBaidu());
NSLog(@"result1:\n%@",result1.value);
JT_AwaitResult *result2 = JT_AWAIT(RequestBing());
NSLog(@"result2:\n%@",result2.value);
JT_AwaitResult *result3 = JT_AWAIT(RequestBaidu());
NSLog(@"result3:\n%@",result3.value);
}];
}
/* 日志输出
2020-09-19 16:39:11.001873+0800 Test[18643:4011489] result1:
BAIDU
2020-09-19 16:39:11.273182+0800 Test[18643:4011489] result2:
BING
2020-09-19 16:39:11.283764+0800 Test[18643:4011489] result3:
BAIDU
*/
我们要使用分两步:
第一步:需要构造一个JTWaitClosure
类型的block。如上面的的请求百度、必应。
第二步:在 JT_AsyncOperation
的async方法的block中,使用JT_AWAIT('任务')的形式,来实现类似async/await的用法。
使用JT_AsyncOperation 的async/await的优点:
- 使任务条理清晰,任务执行有序化
- 后者对于前者的数据依赖简单便捷,避免了万恶的地狱回调。
需要注意的事情:因为主任务队列是异步并发,所以在更新UI相关的操作,需要切换到主线程。
网友评论