美文网首页
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