前言

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

正题

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

 //模拟一个网络请求方法 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. Razor 3、MVC 5

    Razor 3 需要vs 2012 update 4 才可以 需要装一个 Microsoft ASP.NET and Web Tools 2013.1 才会有 MVC 5

  2. Android startActivity()和onActivityResult()使用总结(转载)

    有三个Activity: A.java ,B.java ,C.java Activity之间的跳转常用方法: 1. startActivity(Intent intent); 该方法只用于启动新的Ac ...

  3. Servlet线程安全2

    Servlet的多线程机制 Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的.当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配 ...

  4. 检测手机中是否安装了google地图,没有则提示安装,并跳转到地图查找特定的地点

    /** * 检测手机中是否安装了某个特定的app,若没有提示安装 */ PackageInfo name_2 = null; try { // 若没有这个包名会异常 name_2 = getPacka ...

  5. go语言获取字符串元素的个数

    1:获取字符串字节的个数,并按字节挨个输出 package main import ( "fmt" ) func main() { var str string = "a ...

  6. ASSERT(m_hWnd == hWndOrig) - 解决之

    该问题产生的原因为:创建类型为regular dll 的MFC dll中的窗口创建与其它DLL或EXE中的窗口创建混乱. 比如:dll1 中函数 fun1() 调用 dll2 中函数 fun2(),在 ...

  7. ios如何在当前工程中添加编辑新建的FramesWork

    本文转载至 http://www.apkbus.com/android-131519-1-1.html,感谢原文作者的分享.     naniboy 该用户从未签到   可能很多大牛都见过FaceBo ...

  8. PHP7-MySQLi在分页中的应用

    获取页码,设置每页行数 $page = $_POST["page"]; if($page == ""||$page <= 0){ $page = 1; } ...

  9. 印象笔记Mac端快捷键

  10. 《Python机器学习》笔记(六)

    模型评估与参数调优实战 基于流水线的工作流 一个方便使用的工具:scikit-learn中的Pipline类.它使得我们可以拟合出包含任意多个处理步骤的模型,并将模型用于新数据的预测. 加载威斯康星乳 ...