前言

今天我们来讨论一个经常出现的需求场景,也是一个老话题。在开发中我们往往会遇到需要进行多个网络请求,并且需要多个网络请求成功返回后再做其他事的场景。比如同一个界面显示的内容需要用到两个网络接口,而需求又希望成功返回两个接口的数据再进行页面展示;又比如喜欢挖坑的后台同学就只提供了返回一条数据的接口,但需求却希望我们在一个界面同时显示几条数据的情况。

正题

我们不讨论什么执行完一个请求再执行一个这种串行的低效率方法,以下分析都是在异步的基础上进行的。
废话少说,直奔正题!先上个网络请求的模拟代码。

 //模拟一个网络请求方法 get/post/put...etc
- (void)httpRequest:(NSString *)method param:(NSDictionary *)param completion:(void(^)(id response))block{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
NSString *commend = [param objectForKey:commandKey];
NSLog(@"request:%@ run in thread:%@", commend, [NSThread currentThread]);
NSTimeInterval sleepInterval = arc4random() % ;
[NSThread sleepForTimeInterval:sleepInterval];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"requset:%@ done!", commend);
block(nil);
});
});
}

不可行的直接使用group的方案

对于这样的需求,我们自然而然就想到了使用GCD group,先上代码

 - (void)testUsingGroup1{
NSArray *commandArray = @[@"requestcommand1", @"requestcommand2", @"requestcommand3", @"requestcommand4", @"requestcommand5"];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); [commandArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
dispatch_group_async(group, queue, ^{
NSLog(@"%@ in group thread:%@", obj, [NSThread currentThread]);
[self httpRequest:nil param:@{commandKey : obj} completion:^(id response) { }];
});
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"all http request done!");
NSLog(@"UI update in main thread!");
});
}

代码很快写完了,但却存在问题,我们来看一下运行结果:

 -- ::27.336 TestMutiRequest[:] requestcommand2 in group thread:<NSThread: 0x600000262a40>{number = , name = (null)}
-- ::27.336 TestMutiRequest[:] requestcommand1 in group thread:<NSThread: 0x60000007f2c0>{number = , name = (null)}
-- ::27.336 TestMutiRequest[:] requestcommand3 in group thread:<NSThread: 0x608000263c00>{number = , name = (null)}
-- ::27.336 TestMutiRequest[:] requestcommand4 in group thread:<NSThread: 0x600000262b00>{number = , name = (null)}
-- ::27.336 TestMutiRequest[:] requestcommand5 in group thread:<NSThread: 0x600000262a40>{number = , name = (null)}
-- ::27.336 TestMutiRequest[:] request:requestcommand2 run in thread:<NSThread: 0x60000007f2c0>{number = , name = (null)}
-- ::27.337 TestMutiRequest[:] request:requestcommand1 run in thread:<NSThread: 0x608000264000>{number = , name = (null)}
-- ::27.337 TestMutiRequest[:] request:requestcommand3 run in thread:<NSThread: 0x608000263c00>{number = , name = (null)}
-- ::27.337 TestMutiRequest[:] request:requestcommand4 run in thread:<NSThread: 0x600000262b00>{number = , name = (null)}
-- ::27.338 TestMutiRequest[:] request:requestcommand5 run in thread:<NSThread: 0x600000262a40>{number = , name = (null)}
-- ::27.391 TestMutiRequest[:] all http request done!
-- ::27.429 TestMutiRequest[:] UI update in main thread!
-- ::27.432 TestMutiRequest[:] requset:requestcommand2 done!
-- ::27.435 TestMutiRequest[:] requset:requestcommand3 done!
-- ::27.437 TestMutiRequest[:] requset:requestcommand4 done!
-- ::28.347 TestMutiRequest[:] requset:requestcommand5 done!
-- ::35.399 TestMutiRequest[:] requset:requestcommand1 done!

注意这里:

 -- ::27.338 TestMutiRequest[:] request:requestcommand5 run in thread:<NSThread: 0x600000262a40>{number = , name = (null)}
-- ::27.391 TestMutiRequest[:] all http request done!
-- ::27.429 TestMutiRequest[:] UI update in main thread!
-- ::27.432 TestMutiRequest[:] requset:requestcommand2 done!

结果很明显,并不能得出我们需要的结果!!
问题究竟出现哪呢?!!
让我们在回顾一下group的概念,group的设计就是为了方便我们执行完一系列的任务之后再执行其他的任务,但是不能忽视的是,这里的任务是有要求的,这里的任务必须要是同步执行的!!如果任务是异步的,group只会执行完任务里面异步之前的代码以及分发异步任务就返回了!!也就代表分发group的当前这个任务完成了!但事实却是这个任务的一部分子任务在其他线程执行了,而且不一定已执行结束返回。
问题分析到这里,理所当然的就会出现以上结果的问题。
解决的方案也很自然想法了,就是想办法使分发到group的任务是同步执行的。
顺便提一点,虽然任务未能顺利完成,但我们可以注意到GCD实现的一些细节,在这里group到另外异步方法的执行,GCD并没有重新创建新的线程,而是重用了group已创建的线程。

改进的group方案

这里我们使用dispatch_semaphore_t使单个请求任务同步执行。

 - (void)httpRequest2:(NSString *)method param:(NSDictionary *)param completion:(void(^)(id response))block{
dispatch_semaphore_t sem = dispatch_semaphore_create();
[self httpRequest:method param:param completion:^(id response) {
if (block) {
block(response);
}
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}

testUsingGroup方法也相应改成对httpRequest2方法的调用

 - (void)testUsingGroup2{
NSArray *commandArray = @[@"requestcommand1", @"requestcommand2", @"requestcommand3", @"requestcommand4", @"requestcommand5"];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); [commandArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
dispatch_group_async(group, queue, ^{
NSLog(@"%@ in group thread:%@", obj, [NSThread currentThread]);
[self httpRequest2:nil param:@{commandKey : obj} completion:^(id response) { }];
});
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"all http request done!");
NSLog(@"UI update in main thread!");
});
}

我们再来看一下结果:

 -- ::01.744 TestMutiRequest[:] requestcommand4 in group thread:<NSThread: 0x60000007bfc0>{number = , name = (null)}
-- ::01.744 TestMutiRequest[:] requestcommand2 in group thread:<NSThread: 0x608000073880>{number = , name = (null)}
-- ::01.744 TestMutiRequest[:] requestcommand1 in group thread:<NSThread: 0x60000007ba80>{number = , name = (null)}
-- ::01.744 TestMutiRequest[:] requestcommand3 in group thread:<NSThread: 0x60000007bec0>{number = , name = (null)}
-- ::01.745 TestMutiRequest[:] requestcommand5 in group thread:<NSThread: 0x60000007be80>{number = , name = (null)}
-- ::01.745 TestMutiRequest[:] request:requestcommand4 run in thread:<NSThread: 0x60000007c440>{number = , name = (null)}
-- ::01.746 TestMutiRequest[:] request:requestcommand2 run in thread:<NSThread: 0x60000007c4c0>{number = , name = (null)}
-- ::01.746 TestMutiRequest[:] request:requestcommand1 run in thread:<NSThread: 0x60000007c540>{number = , name = (null)}
-- ::01.746 TestMutiRequest[:] request:requestcommand3 run in thread:<NSThread: 0x60000007c440>{number = , name = (null)}
-- ::01.746 TestMutiRequest[:] request:requestcommand5 run in thread:<NSThread: 0x608000073ec0>{number = , name = (null)}
-- ::01.751 TestMutiRequest[:] requset:requestcommand4 done!
-- ::01.821 TestMutiRequest[:] requset:requestcommand3 done!
-- ::02.817 TestMutiRequest[:] requset:requestcommand1 done!
-- ::03.796 TestMutiRequest[:] requset:requestcommand5 done!
-- ::07.817 TestMutiRequest[:] requset:requestcommand2 done!
-- ::07.818 TestMutiRequest[:] all http request done!
-- ::07.818 TestMutiRequest[:] UI update in main thread!

这个结果就是我们预期希望得到的!
但是不能高兴的太早,这个方法需要实现了我们的需求,但是确实存在问题的。我们可以看一下当前的线程情况。整整比上一种不可行方案多出了一倍的线程数(5条线程->10条线程)!!
这是怎么发生的呢?刚好是因为我们把请求方法改成了同步的,但是网络请求是在其他线程执行的!系统分配给执行httpRequest2的线程必须等待进行具体网络请求的线程执行结束后再能继续执行,否则继续等待,相对上一种方案来说,这个线程就是刚好多出来的线程,这在上一种方案是不存在的。也就是刚好多了一倍。虽然有代价,但是我们的任务也算顺利完成了。

不额外增加多一倍线程的方法(dispatch_semaphore_t)

使用信号量

 - (void)testUsingSemaphore{
dispatch_semaphore_t sem = dispatch_semaphore_create(); NSArray *commandArray = @[@"requestcommand1", @"requestcommand2", @"requestcommand3", @"requestcommand4", @"requestcommand5"]; NSInteger commandCount = [commandArray count];
//代表http访问返回的数量
//这里模仿的http请求block块都是在同一线程(主线程)执行返回的,所以对这个变量的访问不存在资源竞争问题,故不需要枷锁处理
//如果网络请求在不同线程返回,要对这个变量进行枷锁处理,不然很会有资源竞争危险
__block NSInteger httpFinishCount = ;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
//demo testUsingSemaphore方法是在主线程调用的,不直接调用遍历执行,而是嵌套了一个异步,是为了避免主线程阻塞
NSLog(@"start all http dispatch in thread: %@", [NSThread currentThread]);
[commandArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self httpRequest:nil param:@{commandKey : obj} completion:^(id response) {
//全部请求返回才触发signal
if (++httpFinishCount == commandCount) {
dispatch_semaphore_signal(sem);
}
}];
}];
//如果全部请求没有返回则该线程会一直阻塞
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"all http request done! end thread: %@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"UI update in main thread!");
});
}); }

这是种可行的方法,思路很简单:任务分发线程进行等待,所有网络请求成功返回后才发送信号量,任务分发线程继续执行。直接上结果:

 -- ::45.498 TestMutiRequest[:] start all http dispatch in thread: <NSThread: 0x608000260680>{number = , name = (null)}
-- ::45.499 TestMutiRequest[:] request:requestcommand3 run in thread:<NSThread: 0x60000007ec80>{number = , name = (null)}
-- ::45.499 TestMutiRequest[:] request:requestcommand2 run in thread:<NSThread: 0x608000260c00>{number = , name = (null)}
-- ::45.499 TestMutiRequest[:] request:requestcommand1 run in thread:<NSThread: 0x60000007e000>{number = , name = (null)}
-- ::45.499 TestMutiRequest[:] request:requestcommand4 run in thread:<NSThread: 0x608000260d40>{number = , name = (null)}
-- ::45.499 TestMutiRequest[:] request:requestcommand5 run in thread:<NSThread: 0x608000260b80>{number = , name = (null)}
-- ::45.519 TestMutiRequest[:] requset:requestcommand1 done!
-- ::47.500 TestMutiRequest[:] requset:requestcommand4 done!
-- ::49.559 TestMutiRequest[:] requset:requestcommand3 done!
-- ::50.558 TestMutiRequest[:] requset:requestcommand5 done!
-- ::52.571 TestMutiRequest[:] requset:requestcommand2 done!
-- ::52.572 TestMutiRequest[:] all http request done! end thread: <NSThread: 0x608000260680>{number = , name = (null)}
-- ::52.572 TestMutiRequest[:] UI update in main thread!

结论:
从效率和资源使用的角度来看,最后一种只使用信号量的方法是最好的,但是却有点使用一个外部变量来保存执行次数的嫌疑。
另外本文的思路不仅适用于网络请求这种场景,其他需要异步执行的类似场景也是适用的,比如数据库操作等。
个人水平有限,如果你有更好的方案或者觉得不对的地方,随时欢迎在评论留言交流学习!!

最后附上demo地址:
https://github.com/Calvix-Xu/TestMultiRequest.git

作者:Calvix
链接:http://www.jianshu.com/p/46f1314ed947
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

多个网络请求成功返回再执行另外任务的思路分析(iOS)的更多相关文章

  1. iOS 多个异步网络请求全部返回后再执行具体逻辑的方法

    对于dispatch多个异步操作后的同步方法,以前只看过dispatch_group_async,看看这个方法的说明: * @discussion * Submits a block to a dis ...

  2. ajax请求成功但不执行success-function回调函数的问题

    在success:function(data){}下面加个error:function(){},看看是不是出错了走了error.如果是,说明返回值类型不符合要求. 比如:下面代码返回String类型. ...

  3. 网络请求时 返回 App Transport Security has blocked a cleartext HTTP

    如上图,是因为 Xcode7 没有对 plist 进行 http 请求的配置  所致 这时需要  加上上面的plist的红框中 的内容  并且 设置 为 yes  如下图

  4. C# mvc Request 请求过长报404错误的解决思路分析

    案例 我们需要根据index 页面选取值 然后 在弹出页面展示已经选取的值 但其实Request 超出请求长度,后来经过模式解决了. 分享如下 1.设定 web.config 里面的 在web.con ...

  5. Android网络请求框架

    本篇主要介绍一下Android中经常用到的网络请求框架: 客户端网络请求,就是客户端发起网络请求,经过网络框架的特殊处理,让后将请求发送的服务器,服务器根据 请求的参数,返回客户端需要的数据,经过网络 ...

  6. React Native 网络请求封装:使用Promise封装fetch请求

    最近公司使用React作为前端框架,使用了异步请求访问,这里做下总结: React Native中虽然也内置了XMLHttpRequest 网络请求API(也就是俗称的ajax),但XMLHttpRe ...

  7. Flutter用dio封装http网络请求,设置统一的请求地址、headers及处理返回内容

    封装http请求是项目中经常需要做的,常用于设置通用请求地址.请求headers以及处理返回结果,例如在项目中开发地址.测试地址.上线地址是不一样的,当在封装的请求设置好默认地址之后只需要改一个地址而 ...

  8. Fiddler抓取Intellij Idea中执行的web网络请求

    首先可以打开命令行 输入:ipconfig 找到本机配置的IP地址 这里是: 192.168.97.122 或者打开Fiddler 点击如下图片中的小三角符号:将鼠标放在online的位置,也可以看到 ...

  9. iOS - ASIHTTPRequest 网络请求

    前言 使用 iOS SDK 中的 HTTP 网络请求 API,相当的复杂,调用很繁琐,ASIHTTPRequest 就是一个对 CFNetwork API 进行了封装,并且使用起来非常简单的一套 AP ...

随机推荐

  1. mac下安装phpstorm主题

    在<推荐一个phpstorm主题和字体>中介绍了window下如何安装phpstorm主题,这里我们在介绍一下如何在MAC下安装phpstorm主题. 安装方法和window类似,只是需要 ...

  2. linux 免密登录

    ssh-keygen -t rsa -P "" ssh-copy-id -i ~/.ssh/id_rsa.pub root@服务器地址

  3. FFMPEG中关于ts流的时长估计的实现

    ts流中的时间估计 我们知道ts流中是没有时间信息的,我门来看看ffmpeg是怎么估计其duration的 方法1.通过pts来估计 static void estimate_timings_from ...

  4. Servlet 编写过滤器

    Servlet 过滤器可以动态地拦截请求和响应,以变换或使用包含在请求或响应中的信息. 可以将一个或多个 Servlet 过滤器附加到一个 Servlet 或一组 Servlet.Servlet 过滤 ...

  5. Jmeter中中文乱码

    jmeter-察看结果树-响应数据中的中文显示乱码 jmeter\bin\jmeter.properties 默认编码为:ISO-8859-1# The encoding to be used if ...

  6. GUN C中的socket学习(一)

    socket是用于通信的工具. 套接字其实是一个广义上的进程间通信的信道.就像pipe一样,在GUN环境下socket也被用一个文件表示.不同的socket文件可以用于不同的进程间通信,甚至可以用来在 ...

  7. YII配置rabbitMQ时前期工作各种坑

    背景如下: 项目需要做一个订阅/发布的功能,然后一大堆讨论不做说明,确认使用rabbitMQ来做: okay,既然 要这个来做,我们下载这个东西吧!在官网上下载就okay了,不做说明,下载安装的时候会 ...

  8. 【BZOJ2730】[HNOI2012]矿场搭建 Tarjan

    [BZOJ2730][HNOI2012]矿场搭建 Description 煤矿工地可以看成是由隧道连接挖煤点组成的无向图.为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处. ...

  9. 1070 Bash游戏 V4

    1070 Bash游戏 V4 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题 有一堆石子共有N个.A B两个人轮流拿,A先拿.每次拿的数量最少1个,最多不超过对手上 ...

  10. IDEA中的lombok插件安装以及各注解的详细介绍

    IDEA中的lombok插件安装以及各注解的详细介绍 其实对于我们来说, 写好实体类后,直接用快捷方式生成get,set方法,还有 构造方法就行了,但是对于字段比较多的, 如果修改一个属性的话,就要再 ...