美文网首页
iOS类似async/await的用法

iOS类似async/await的用法

作者: 落入粪池的凤凰 | 来源:发表于2020-09-19 17:05 被阅读0次

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分别是valuedone来表示返回值以及是否迭代完毕。
  • 可以看出第一调用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相关的操作,需要切换到主线程。

相关文章

网友评论

      本文标题:iOS类似async/await的用法

      本文链接:https://www.haomeiwen.com/subject/ordsyktx.html