美文网首页程序员iOS技术点iOS开发攻城狮的集散地
iOS并发请求一次结果回调的处理方案

iOS并发请求一次结果回调的处理方案

作者: 翰霖啊 | 来源:发表于2018-10-12 16:25 被阅读14次

实际工作中我们经常会遇到有接口需要同时返回请求结果的情况,比如某一个详情页,可能有详情信息和评论信息等多个接口需要请求,并且当多个接口全部完成的时候,刷新当前页面的数据,这里由于请求是异步的关系,我们不知道具体哪个请求会需要多久时间才能完成,所以今天分析一下解决方案。
在之前的《GCD的使用和原理》一文中有简单讲述这个问题,本文会针对这个问题详细讨论。
首先一个案例,如果存在多个异步处理,如何能知道同时完成呢,这里能想到的是使用dispatch_group_async的方式将异步处理放入一个组中,再使用dispatch_group_notify获得所有组中异步完成的通知回调。

    NSLog(@"全部开始-----%@", [NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        sleep(4);
        NSLog(@"子线程1-----%@", [NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        sleep(3);
        NSLog(@"子线程2-----%@", [NSThread currentThread]);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"全部结束-----%@", [NSThread currentThread]);
    });

这里简单模拟了异步的耗时操作,我们需要的结果是两个子线程的任务全部完成之后,才回到主线程继续任务,下面看一下打印结果

全部开始-----<NSThread: 0x6040000741c0>{number = 1, name = main}
子线程2-----<NSThread: 0x604000277380>{number = 5, name = (null)}
子线程1-----<NSThread: 0x604000469080>{number = 4, name = (null)}
全部结束-----<NSThread: 0x6040000741c0>{number = 1, name = main}

这里可以很明显的看出开始和结束都是主线程,而结束之前的确执行了两个子线程的耗时任务

这里实现了我们的需求,当然不只是一种方式可以实现,下面再尝试一种方法看看。

    NSLog(@"全部开始-----%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_async(queue, ^{
        sleep(4);
        NSLog(@"子线程1-----%@", [NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_async(queue, ^{
        sleep(3);
        NSLog(@"子线程2-----%@", [NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"全部结束-----%@", [NSThread currentThread]);

这次没有使用dispatch_group的方式,这个semaphore应该也比较常用了(如果不常用的可以看我之前的关于GCD那篇文章),我们使用信号量的方式,使用wait阻止主线程后续任务的开展,当信号量不为0的时候会-1并继续执行,如果信号量为0,则等待,在得到信号增加之前就会持续等待,后面参数为时间类型表示永久,当每个异步任务完成后,会给semaphore信号量+1,当所有任务完成之后,主线程不再被阻止,会继续任务。打印结果同上,这里不重复写了,好奇的小伙伴们可以试下。

以上使用了两种方式解决异步任务的全部完成通知,下面该解决本文的需求了,就是当多个请求全部完成的通知该怎么获得。
有些朋友好奇多个请求和多个异步任务有什么区别,这里解释下,多个异步任务我们可以指定这几个异步任务为同一个组的任务,以及通过组的notify方法得到任务结束的通知,但是多个请求没有这种条件,所以不能完全用之前的方式处理。
先给一下错误示范案例:

    NSLog(@"即将开始多个请求");
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_async(group, queue, ^{
        [[URLBase sharedInstance] getBusinessDataWithSuccess:^(NSDictionary *result) {
            NSLog(@"请求1完成");
        } failure:^(NSError *error) {
        }];
    });
    dispatch_group_async(group, queue, ^{
        [[URLBase sharedInstance] getHomeConfigurationWithSuccess:^(NSDictionary *result) {
            NSLog(@"请求2完成");
        } failure:^(NSError *error) {
        }];
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"全部完成");
    });

这里用了两个请求任务放入异步操作中,下面看打印结果是否和我们预想的一样

2018-09-11 22:43:04.469787+0800 Demo[2511:1215843] 即将开始多个请求
2018-09-11 22:43:04.472144+0800 Demo[2511:1216152] 全部完成
2018-09-11 22:43:05.598410+0800 Demo[2511:1215843] 请求1完成
2018-09-11 22:43:05.604425+0800 Demo[2511:1215843] 请求2完成

这里的时间很明显告诉我们,同时返回的通知并没有完成,原因是当我们使用请求的时候,相当于又一次开启了一个异步操作,请求的代码会立即执行完成,但是请求完成的异步回调并不能,所以我们要稍微改良一下我们的代码

NSLog(@"即将开始多个请求");
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_async(group, queue, ^{
        dispatch_group_enter(group);
        [[URLBase sharedInstance] getBusinessDataWithSuccess:^(NSDictionary *result) {
            dispatch_group_leave(group);
            NSLog(@"请求1完成");
        } failure:^(NSError *error) {
        }];
    });
    
    dispatch_group_async(group, queue, ^{
        dispatch_group_enter(group);
        [[URLBase sharedInstance] getHomeConfigurationWithSuccess:^(NSDictionary *result) {
            dispatch_group_leave(group);
            NSLog(@"请求2完成");
        } failure:^(NSError *error) {
        }];
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"全部完成");
    });

我们的改动很小,只有在请求之前加了enter函数,以及请求成功之后加入leave函数,下面看一下成功的结果

2018-09-11 22:48:41.626897+0800 Gemii[2518:1218253] 即将开始多个请求
2018-09-11 22:48:45.881502+0800 Gemii[2518:1218253] 请求1完成
2018-09-11 22:48:48.982129+0800 Gemii[2518:1218253] 请求2完成
2018-09-11 22:48:48.984066+0800 Gemii[2518:1218425] 全部完成

很明显我们的需求达到了,解决方案就是当请求之前调用dispatch_group_enter()函数,表明即将进入另一个内部操作中,后续任务暂时停止,直到请求成功之后,调用dispatch_group_leave()函数,表明另一个内部操作完成,可以进行接下来的操作,于是两个group_async同时完成,调用notify通知

上面很清楚我们用dispatch_group_enter和dispatch_group_leave这一对函数实现了我们的需求,下面仍然用semaphore的方式处理这个问题,不过semaphore如果像之前一样操作,同样会出问题,案例如下

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    NSLog(@"即将开始多个请求");

    [[URLBase sharedInstance] getBusinessDataWithSuccess:^(NSDictionary *result) {
        NSLog(@"请求1完成");
        dispatch_semaphore_signal(semaphore);
    } failure:^(NSError *error) {
    }];
    [[URLBase sharedInstance] getHomeConfigurationWithSuccess:^(NSDictionary *result) {
        NSLog(@"请求2完成");
        dispatch_semaphore_signal(semaphore);
    } failure:^(NSError *error) {
    }];
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"全部完成");

这里的代码也很清晰,和前文一样,我们认为当请求成功之后,将信号量+1,直到两个请求都完成解决wait的阻塞,但是我们看一下结果

2018-09-11 23:12:55.898884+0800 Gemii[2600:1231519] 即将开始多个请求

这里只打印了开始的部分,后续任务全部没有执行,并且页面进入了死锁状态,造成这个的原因是什么呢,看似和之前一样,应该没有问题,但是这里注意,我们主线程一开始就已经进入了wait状态,之前的方式是在异步的子线程中,增加信号量,但是我们这里想回到主线程的block已经被wait挡住不能执行,导致不能调用dispatch_semaphore_signal()函数增加信号量,所以造成了死锁,那么解决办法就是将请求放到异步队列中

NSLog(@"即将开始多个请求");
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        [[URLBase sharedInstance] getBusinessDataWithSuccess:^(NSDictionary *result) {
            NSLog(@"请求1完成---%@", [NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        } failure:^(NSError *error) {
        }];
    });
    dispatch_group_async(group, queue, ^{
        [[URLBase sharedInstance] getHomeConfigurationWithSuccess:^(NSDictionary *result) {
            NSLog(@"请求2完成---%@", [NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        } failure:^(NSError *error) {
        }];
    });
    
    dispatch_group_notify(group, queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"全部完成---%@", [NSThread currentThread]);
    });

这里我们虽然使用了group的方式,但是没有使用enter和leave两个函数,而是为了避免semaphore会在主线程死锁而加入了异步操作,下面是成功结果

2018-09-11 23:25:30.054214+0800 即将开始多个请求
2018-09-11 23:25:30.705859+0800 请求1完成---<NSThread: 0x1c02623c0>{number = 1, name = main}
2018-09-11 23:25:30.905989+0800 请求2完成---<NSThread: 0x1c02623c0>{number = 1, name = main}
2018-09-11 23:25:30.906441+0800 全部完成---<NSThread: 0x1c0870d40>{number = 11, name = (null)}

这样看就很清晰,两个请求完成是在主线程完成的回调,但是wait以及完成后操作,均在子线程中,不会对主线程造成阻塞,所以可以实现本文需求

到这里我使用了两种方式实现标题的需求,有更多的解决办法还期待大家的探索,这里就不多加赘述了,如果有更方便的方式,欢迎在下方评论区进行讨论,如果文章中有技术错误还请指正。
按照之前说的,后续文章会讨论多个请求同步执行的方法,最近时间比较紧,更新比较慢还请见谅,如果有认为本文有些作用的请点个赞支持下,感谢各方大佬~

相关文章

  • iOS并发请求一次结果回调的处理方案

    实际工作中我们经常会遇到有接口需要同时返回请求结果的情况,比如某一个详情页,可能有详情信息和评论信息等多个接口需要...

  • 优雅地处理网络请求的依赖关系

    处理网络时,经常会遇到网络请求相互依赖的情况,如B请求的参数依赖A请求的回调结果,直接在A请求的回调中请求B,会导...

  • Vue项目 --- ajax插件axios

    axios安装 引入 请求数据 url是请求的接口地址,func是对返回结果进行处理的回调函数 例子:

  • 关于多个网络请求同步的一些总结

    等待多个并发请求同步回调 例如同时发起网络请求A,网络请求B,网络请求C,需等待A,B,C都返回了才进行回调。曾经...

  • ios socket 请求结果 回调 优化

    近期玩socket 的时候,想着如何能做到和http一样的一个请求回调一个Block。以下方案只是抛砖引玉,欢迎广...

  • Ajax 请求中的回调

    Ajax 请求中的回调 创建 Ajax 对象(可以通过函数创建) 指定结果处理器ajax.onreadystate...

  • node 回调函数

    概念 回调函数在 完成任务后 被 调用。解决阻塞或等待I/O操作,处理大量并发请求。 阻塞代码 非阻塞代码 总结 ...

  • RabbitMQ的RPC实现

    基本概念 Callback queue 回调队列 一个客户端向服务器发送请求,服务器端处理请求后,将其处理结果保存...

  • JS异步-解决方法简述

    介绍三种异步处理方案: 回调函数(callback)promiseasync/await 回调函数(callbac...

  • IMP用法

    /// 请求回调 通用处理 - (void)makeCommonResponse:(id)responseObje...

网友评论

    本文标题:iOS并发请求一次结果回调的处理方案

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