iOS并发请求一次结果回调的解决方案

  • 时间:2018-10-12 23:17 作者:Howie灬 来源:Howie灬 阅读:137
  • 扫一扫,手机访问
摘要:实际工作中我们经常会遇到有接口需要同时返回请求结果的情况,比方某一个介绍页,可能有介绍信息和评论信息等多个接口需要请求,并且当多个接口一律完成的时候,刷新当前页面的数据,这里因为请求是异步的关系,我们不知道具体哪个请求会需要多久时间才能完成,所以今天分析一下处理方案。在之前的《GCD的使用和原理》一

实际工作中我们经常会遇到有接口需要同时返回请求结果的情况,比方某一个介绍页,可能有介绍信息和评论信息等多个接口需要请求,并且当多个接口一律完成的时候,刷新当前页面的数据,这里因为请求是异步的关系,我们不知道具体哪个请求会需要多久时间才能完成,所以今天分析一下处理方案。
在之前的《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以及完成后操作,均在子线程中,不会对主线程造成阻塞,所以可以实现本文需求

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

  • 全部评论(0)
最新发布的资讯信息
【系统环境|服务器应用】树莓派安装TensorFlow(2020-04-24 21:11)
【系统环境|服务器应用】防面试-SD_WebImage(2020-04-24 21:11)
【系统环境|服务器应用】推荐一款视频控件xgplayer(2020-04-24 21:11)
【系统环境|服务器应用】PostgreSQL 源码解读(27)- 查询语句#12(查询优化-上拉子链接#2)(2020-04-24 21:11)
【系统环境|服务器应用】如何轻松学习JavaScript?(2020-04-24 21:10)
【系统环境|服务器应用】【源码剖析】Launcher 8.0 源码 (12) --- Launcher 启动流程 第五步之计算桌面各布局细节参数(2020-04-24 21:10)
【系统环境|服务器应用】前台碰撞室之console.log与文本字符(2020-04-24 21:10)
【系统环境|服务器应用】好用的Middleware实现(2020-04-24 21:10)
【系统环境|服务器应用】前台面试每日 3+1 —— 第373天(2020-04-24 21:10)
【系统环境|服务器应用】绍圣--kafka之生产者(五)(2020-04-24 21:10)
手机二维码手机访问领取大礼包
返回顶部