iOS当多个网络请求完成后执行下一步的方法详解
前言
在开发中,我们很容易遇到这样的需求,需要我们同时做多个网络请求,所有网络请求都完成后才能进行下一步的操作。
网络请求的任务是提交给子线程异步处理了,网络请求这样的任务也就快速执行完毕了,但是网络请求是一个任务,处理收到的网络响应又是一个任务,注意不要把这两个过程混为一谈。
如下载多个图片,下载完了才能展示,今天我们就来研究一下这个问题的解决方案。
解决方法
1.首先,我们创建一个项目,然后做一般性的做法,不做任何处理去连续请求一个接口10次:
先在viewdidload中创建第一种情况.
//1.无处理 uibutton *btn1 = [uibutton buttonwithtype:uibuttontypecustom]; btn1.frame = cgrectmake(100, 100, 100, 40); btn1.backgroundcolor = [uicolor graycolor]; [btn1 settitle:@"noconduct" forstate:uicontrolstatenormal]; [btn1 addtarget:self action:@selector(btn1) forcontrolevents:uicontroleventtouchupinside]; [self.view addsubview:btn1];
实现第一种情况的方法
//1.noconduct -(void)btn1{ nsstring *str = @"http://www.jianshu.com/p/6930f335adba"; nsurl *url = [nsurl urlwithstring:str]; nsurlrequest *request = [nsurlrequest requestwithurl:url]; nsurlsession *session = [nsurlsession sharedsession]; for (int i=0; i<10; i++) { nsurlsessiondatatask *task = [session datataskwithrequest:request completionhandler:^(nsdata * _nullable data, nsurlresponse * _nullable response, nserror * _nullable error) { nslog(@"%d---%d",i,i); }]; [task resume]; } nslog(@"end"); }
运行,看看我们的控制台输出:
2017-12-04 17:10:10.503 downimage[3289:261033] end 2017-12-04 17:10:10.676 downimage[3289:261080] 0---0 2017-12-04 17:10:10.704 downimage[3289:261080] 1---1 2017-12-04 17:10:10.754 downimage[3289:261096] 4---4 2017-12-04 17:10:10.760 downimage[3289:261080] 2---2 2017-12-04 17:10:10.800 downimage[3289:261096] 5---5 2017-12-04 17:10:10.840 downimage[3289:261080] 7---7 2017-12-04 17:10:10.844 downimage[3289:261082] 6---6 2017-12-04 17:10:10.846 downimage[3289:261096] 3---3 2017-12-04 17:10:10.888 downimage[3289:261096] 8---8 2017-12-04 17:10:10.945 downimage[3289:261080] 9---9
很明显,无任何处理情况下,end最先被打印出来,由于网络请求的异步回调,然后各个网络请求的回调顺序是无序的。
2.使用gcd的dispatch_group_t
viewdidload里:
//2.group uibutton *btn2 = [uibutton buttonwithtype:uibuttontypecustom]; btn2.frame = cgrectmake(100, 200, 100, 40); btn2.backgroundcolor = [uicolor graycolor]; [btn2 settitle:@"group--" forstate:uicontrolstatenormal]; [btn2 addtarget:self action:@selector(btn2) forcontrolevents:uicontroleventtouchupinside]; [self.view addsubview:btn2];
实现:
//2.group-- -(void)btn2{ nsstring *str = @"http://www.jianshu.com/p/6930f335adba"; nsurl *url = [nsurl urlwithstring:str]; nsurlrequest *request = [nsurlrequest requestwithurl:url]; nsurlsession *session = [nsurlsession sharedsession]; dispatch_group_t downloadgroup = dispatch_group_create(); for (int i=0; i<10; i++) { dispatch_group_enter(downloadgroup); nsurlsessiondatatask *task = [session datataskwithrequest:request completionhandler:^(nsdata * _nullable data, nsurlresponse * _nullable response, nserror * _nullable error) { nslog(@"%d---%d",i,i); dispatch_group_leave(downloadgroup); }]; [task resume]; } dispatch_group_notify(downloadgroup, dispatch_get_main_queue(), ^{ nslog(@"end"); }); }
运行看看控制台输出:
2017-12-04 17:14:46.984 downimage[3289:265374] 2---2 2017-12-04 17:14:46.987 downimage[3289:265370] 1---1 2017-12-04 17:14:47.052 downimage[3289:265383] 5---5 2017-12-04 17:14:47.065 downimage[3289:265370] 4---4 2017-12-04 17:14:47.111 downimage[3289:265379] 3---3 2017-12-04 17:14:47.121 downimage[3289:265383] 6---6 2017-12-04 17:14:47.169 downimage[3289:265383] 7---7 2017-12-04 17:14:47.192 downimage[3289:265370] 9---9 2017-12-04 17:14:47.321 downimage[3289:265383] 8---8 2017-12-04 17:14:47.747 downimage[3289:265374] 0---0 2017-12-04 17:14:47.747 downimage[3289:261033] end
2017-12-04 17:15:14.576 downimage[3289:265942] 3---3 2017-12-04 17:15:14.626 downimage[3289:265936] 2---2 2017-12-04 17:15:14.647 downimage[3289:265944] 4---4 2017-12-04 17:15:14.648 downimage[3289:265936] 0---0 2017-12-04 17:15:14.657 downimage[3289:265943] 1---1 2017-12-04 17:15:14.709 downimage[3289:265944] 5---5 2017-12-04 17:15:14.728 downimage[3289:265944] 6---6 2017-12-04 17:15:14.734 downimage[3289:265944] 7---7 2017-12-04 17:15:14.738 downimage[3289:265943] 8---8 2017-12-04 17:15:14.816 downimage[3289:265944] 9---9 2017-12-04 17:15:14.816 downimage[3289:261033] end
从上两次输出可以看出,end确实是在所有网络请求之后才输出出来,这也是符合了我们的需求。
代码中我们只添加了4行代码
dispatch_group_t downloadgroup = dispatch_group_create(); dispatch_group_enter(downloadgroup); dispatch_group_leave(downloadgroup); dispatch_group_notify(downloadgroup, dispatch_get_main_queue(), ^{ });
对以上4行代码可理解为:创建一个dispatch_group_t, 每次网络请求前先dispatch_group_enter,请求回调后再dispatch_group_leave,对于enter和leave必须配合使用,有几次enter就要有几次leave,否则group会一直存在。当所有enter的block都leave后,会执行dispatch_group_notify的block。
3.使用gcd的信号量dispatch_semaphore_t
//3.semaphore uibutton *btn3 = [uibutton buttonwithtype:uibuttontypecustom]; btn3.frame = cgrectmake(100, 300, 100, 40); btn3.backgroundcolor = [uicolor graycolor]; [btn3 settitle:@"semaphore" forstate:uicontrolstatenormal]; [btn3 addtarget:self action:@selector(btn3) forcontrolevents:uicontroleventtouchupinside]; [self.view addsubview:btn3];
//3.semaphore-- -(void)btn3{ nsstring *str = @"http://www.jianshu.com/p/6930f335adba"; nsurl *url = [nsurl urlwithstring:str]; nsurlrequest *request = [nsurlrequest requestwithurl:url]; nsurlsession *session = [nsurlsession sharedsession]; dispatch_semaphore_t sem = dispatch_semaphore_create(0); for (int i=0; i<10; i++) { nsurlsessiondatatask *task = [session datataskwithrequest:request completionhandler:^(nsdata * _nullable data, nsurlresponse * _nullable response, nserror * _nullable error) { nslog(@"%d---%d",i,i); count++; if (count==10) { dispatch_semaphore_signal(sem); count = 0; } }]; [task resume]; } dispatch_semaphore_wait(sem, dispatch_time_forever); dispatch_async(dispatch_get_main_queue(), ^{ nslog(@"end"); }); }
运行看看控制台输出:
2017-12-04 17:36:49.098 downimage[3428:283651] 2---2 2017-12-04 17:36:49.144 downimage[3428:284210] 0---0 2017-12-04 17:36:49.152 downimage[3428:284213] 3---3 2017-12-04 17:36:49.158 downimage[3428:283651] 1---1 2017-12-04 17:36:49.167 downimage[3428:284210] 4---4 2017-12-04 17:36:49.235 downimage[3428:284213] 8---8 2017-12-04 17:36:49.249 downimage[3428:283651] 5---5 2017-12-04 17:36:49.252 downimage[3428:283651] 7---7 2017-12-04 17:36:49.324 downimage[3428:283651] 9---9 2017-12-04 17:36:49.468 downimage[3428:284214] 6---6 2017-12-04 17:36:49.469 downimage[3428:283554] end
2017-12-04 17:37:11.554 downimage[3428:284747] 0---0 2017-12-04 17:37:11.555 downimage[3428:284733] 1---1 2017-12-04 17:37:11.627 downimage[3428:284748] 5---5 2017-12-04 17:37:11.661 downimage[3428:284748] 2---2 2017-12-04 17:37:11.688 downimage[3428:284747] 4---4 2017-12-04 17:37:11.709 downimage[3428:284747] 6---6 2017-12-04 17:37:11.770 downimage[3428:284733] 7---7 2017-12-04 17:37:11.774 downimage[3428:284733] 8---8 2017-12-04 17:37:11.824 downimage[3428:284747] 9---9 2017-12-04 17:37:11.899 downimage[3428:284733] 3---3 2017-12-04 17:37:11.900 downimage[3428:283554] end
从输出可以看出,这样的方法也是满足我们的需求的,在这个方法中,我们使用了
dispatch_semaphore_t sem = dispatch_semaphore_create(0); dispatch_semaphore_signal(sem); dispatch_semaphore_wait(sem, dispatch_time_forever);
对这三句代码可以这样理解:dispatch_semaphore信号量为基于计数器的一种多线程同步机制。如果semaphore计数大于等于1,计数-1,返回,程序继续运行。如果计数为0,则等待。dispatch_semaphore_signal(semaphore)
为计数+1操作,dispatch_semaphore_wait(sema, dispatch_time_forever)
为设置等待时间,这里设置的等待时间是一直等待。
对于以上代码通俗一点就是,开始为0,等待,等10个网络请求都完成了,dispatch_semaphore_signal(semaphore)为计数+1,然后计数-1返回,程序继续执行。 (这里也就是为什么有个count变量的原因,记录网络回调的次数,回调10次之后再发信号量,使后面程序继续运行)。
4.考虑新需求,10个网络请求顺序回调。
需求需要顺序回调,即执行完第一个网络请求后,第二个网络请求回调才可被执行,简单来讲就是输出得是0,1,2,3...9这种方式的。
对于这个需求我也是根据自己最近做的项目来提的,因为网络请求回调的异步性,我们虽可以控制网络请求的顺序执行,却不能控制它的完成回调顺序。这就有点伤了,目前我项目是找到了解决方案,但这个问题还没有找到解决办法,提出来跟大家讨论一下。(请忽略网络请求执行,回调,在回调里请求下一个接口的办法,讨论还有没有别的方法,最好show the code).
最后,贴点nsoperation的代码,为了解决新需求所写,由于网络请求回调异步性不能满足需求,但若不是网络请求等异步回调的方式,这样的做法是可以的,大家可以试试.
//4.nsoperation uibutton *btn4 = [uibutton buttonwithtype:uibuttontypecustom]; btn4.frame = cgrectmake(100, 400, 100, 40); btn4.backgroundcolor = [uicolor graycolor]; [btn4 settitle:@"nsoperation" forstate:uicontrolstatenormal]; [btn4 addtarget:self action:@selector(btn4) forcontrolevents:uicontroleventtouchupinside]; [self.view addsubview:btn4];
//4.nsoperation -(void)btn4{ nsstring *str = @"http://www.jianshu.com/p/6930f335adba"; nsurl *url = [nsurl urlwithstring:str]; nsurlrequest *request = [nsurlrequest requestwithurl:url]; nsurlsession *session = [nsurlsession sharedsession]; nsmutablearray *operationarr = [[nsmutablearray alloc]init]; for (int i=0; i<10; i++) { nsblockoperation *operation = [nsblockoperation blockoperationwithblock:^{ nsurlsessiondatatask *task = [session datataskwithrequest:request completionhandler:^(nsdata * _nullable data, nsurlresponse * _nullable response, nserror * _nullable error) { nslog(@"%d---%d",i,i); }]; [task resume]; //非网络请求 nslog(@"norequest-%d",i); }]; [operationarr addobject:operation]; if (i>0) { nsblockoperation *operation1 = operationarr[i-1]; nsblockoperation *operation2 = operationarr[i]; [operation2 adddependency:operation1]; } } nsoperationqueue *queue = [[nsoperationqueue alloc]init]; [queue addoperations:operationarr waituntilfinished:no]; //yes会阻塞当前线程 #warning - 绝对不要在应用主线程中等待一个operation,只能在第二或次要线程中等待。阻塞主线程将导致应用无法响应用户事件,应用也将表现为无响应。 }
运行结果:
2017-12-04 18:03:10.224 downimage[3584:304363] norequest-0 2017-12-04 18:03:10.226 downimage[3584:304362] norequest-1 2017-12-04 18:03:10.226 downimage[3584:304363] norequest-2 2017-12-04 18:03:10.231 downimage[3584:304363] norequest-3 2017-12-04 18:03:10.232 downimage[3584:304362] norequest-4 2017-12-04 18:03:10.233 downimage[3584:304362] norequest-5 2017-12-04 18:03:10.233 downimage[3584:304363] norequest-6 2017-12-04 18:03:10.234 downimage[3584:304363] norequest-7 2017-12-04 18:03:10.235 downimage[3584:304363] norequest-8 2017-12-04 18:03:10.236 downimage[3584:304363] norequest-9 2017-12-04 18:03:10.408 downimage[3584:304597] 2---2 2017-12-04 18:03:10.408 downimage[3584:304597] 0---0 2017-12-04 18:03:10.409 downimage[3584:304597] 1---1 2017-12-04 18:03:10.461 downimage[3584:304597] 5---5 2017-12-04 18:03:10.476 downimage[3584:304363] 4---4 2017-12-04 18:03:10.477 downimage[3584:304365] 6---6 2017-12-04 18:03:10.518 downimage[3584:304365] 7---7 2017-12-04 18:03:10.537 downimage[3584:304596] 8---8 2017-12-04 18:03:10.547 downimage[3584:304362] 9---9 2017-12-04 18:03:11.837 downimage[3584:304362] 3---3
2017-12-04 18:04:27.699 downimage[3584:306401] norequest-0 2017-12-04 18:04:27.700 downimage[3584:306405] norequest-1 2017-12-04 18:04:27.701 downimage[3584:306401] norequest-2 2017-12-04 18:04:27.701 downimage[3584:306405] norequest-3 2017-12-04 18:04:27.702 downimage[3584:306401] norequest-4 2017-12-04 18:04:27.702 downimage[3584:306405] norequest-5 2017-12-04 18:04:27.703 downimage[3584:306401] norequest-6 2017-12-04 18:04:27.703 downimage[3584:306401] norequest-7 2017-12-04 18:04:27.704 downimage[3584:306401] norequest-8 2017-12-04 18:04:27.704 downimage[3584:306401] norequest-9 2017-12-04 18:04:27.772 downimage[3584:306397] 2---2 2017-12-04 18:04:27.779 downimage[3584:306401] 0---0 2017-12-04 18:04:27.782 downimage[3584:306409] 1---1 2017-12-04 18:04:27.800 downimage[3584:306405] 3---3 2017-12-04 18:04:27.851 downimage[3584:306401] 6---6 2017-12-04 18:04:27.855 downimage[3584:306397] 5---5 2017-12-04 18:04:27.915 downimage[3584:306397] 7---7 2017-12-04 18:04:27.951 downimage[3584:306397] 9---9 2017-12-04 18:04:27.953 downimage[3584:306405] 8---8 2017-12-04 18:04:28.476 downimage[3584:306409] 4---4
5.还是使用信号量semaphore完成4的需求
//5.semaphore---order uibutton *btn5 = [uibutton buttonwithtype:uibuttontypecustom]; btn5.frame = cgrectmake(100, 500, 100, 40); btn5.backgroundcolor = [uicolor graycolor]; [btn5 settitle:@"order" forstate:uicontrolstatenormal]; [btn5 addtarget:self action:@selector(btn5) forcontrolevents:uicontroleventtouchupinside]; [self.view addsubview:btn5];
//5.semaphore--order -(void)btn5{ nsstring *str = @"http://www.jianshu.com/p/6930f335adba"; nsurl *url = [nsurl urlwithstring:str]; nsurlrequest *request = [nsurlrequest requestwithurl:url]; nsurlsession *session = [nsurlsession sharedsession]; dispatch_semaphore_t sem = dispatch_semaphore_create(0); for (int i=0; i<10; i++) { nsurlsessiondatatask *task = [session datataskwithrequest:request completionhandler:^(nsdata * _nullable data, nsurlresponse * _nullable response, nserror * _nullable error) { nslog(@"%d---%d",i,i); dispatch_semaphore_signal(sem); }]; [task resume]; dispatch_semaphore_wait(sem, dispatch_time_forever); } dispatch_async(dispatch_get_main_queue(), ^{ nslog(@"end"); }); }
我们看看运行结果:
2017-12-05 10:17:28.175 downimage[938:51296] 0---0 2017-12-05 10:17:28.331 downimage[938:51289] 1---1 2017-12-05 10:17:28.506 downimage[938:51289] 2---2 2017-12-05 10:17:28.563 downimage[938:51289] 3---3 2017-12-05 10:17:28.662 downimage[938:51289] 4---4 2017-12-05 10:17:28.733 downimage[938:51296] 5---5 2017-12-05 10:17:28.792 downimage[938:51296] 6---6 2017-12-05 10:17:28.856 downimage[938:51286] 7---7 2017-12-05 10:17:29.574 downimage[938:51289] 8---8 2017-12-05 10:17:29.652 downimage[938:51286] 9---9 2017-12-05 10:17:29.653 downimage[938:45252] end
2017-12-05 10:17:46.341 downimage[938:51608] 0---0 2017-12-05 10:17:47.967 downimage[938:51607] 1---1 2017-12-05 10:17:48.038 downimage[938:51603] 2---2 2017-12-05 10:17:48.132 downimage[938:51603] 3---3 2017-12-05 10:17:48.421 downimage[938:51608] 4---4 2017-12-05 10:17:48.537 downimage[938:51289] 5---5 2017-12-05 10:17:48.646 downimage[938:51289] 6---6 2017-12-05 10:17:48.939 downimage[938:51289] 7---7 2017-12-05 10:17:50.537 downimage[938:51607] 8---8 2017-12-05 10:17:50.615 downimage[938:51289] 9---9 2017-12-05 10:17:50.616 downimage[938:45252] end
我们对比 3 的代码,3 中我们只在最后也就是循环结束后调用dispatch_semaphore_wait(sem, dispatch_time_forever)
,循环中当网络请求回调10次(也就是都回调完)后,使传入的信号量加1:( dispatch_semaphore_signal(sem) )
,这时等待结束,然后进行后续的操作。
在这个方法里,我们每一次遍历,都让其dispatch_semaphore_wait(sem, dispatch_time_forever)
,这个时候线程会等待,阻塞当前线程,直到dispatch_semaphore_signal(sem)
调用之后,而我们dispatch_semaphore_signal(sem)
是在网络请求的回调里调用的,所以这个方法的逻辑是:
遍历—>发起任务—>等待—>任务完成信号量加1—>等待结束,开始下一个任务
发起任务—>等待—>任务完成信号量加1—>等待结束,开始下一个任务
发起任务—>等待—>任务完成信号量加1—>等待结束,开始下一个任务
这样循环的模式,一个任务完成才能接着去做下面的任务,满足我们的需求。
但我们也要发现这样一个问题,我们使用这种方式,可以明显感觉出整个过程需要花费的时间大大增加了,不像我们 3 中同时(几乎)开启任务等待完成回调,这里是一个网络请求发出,等待,完成后发出第二个网络请求,等待,完成后再发出第三个,这样我们等待的时间是10个网络请求每一个回调时间的和,在时间上大大增加了消耗,而且对于dispatch_semaphore_wait(sem, dispatch_time_forever)
,它是会阻塞线程的,我们如果需要在网络请求完成后修改ui,那这种方式会影响我们的界面交互,接下来我们对比一下两者时间消耗:
3-------------3----------3------- 2017-12-05 10:29:51.178 downimage[938:56971] 2---2 2017-12-05 10:29:51.193 downimage[938:57200] 0---0 2017-12-05 10:29:51.202 downimage[938:56631] 3---3 2017-12-05 10:29:51.248 downimage[938:56971] 1---1 2017-12-05 10:29:51.262 downimage[938:56971] 5---5 2017-12-05 10:29:51.291 downimage[938:56631] 6---6 2017-12-05 10:29:51.375 downimage[938:56631] 7---7 2017-12-05 10:29:51.384 downimage[938:56631] 4---4 2017-12-05 10:29:51.434 downimage[938:56971] 8---8 2017-12-05 10:29:51.487 downimage[938:57199] 9---9 2017-12-05 10:29:51.488 downimage[938:45252] end 5-------------5----------5------- 2017-12-05 10:29:52.190 downimage[938:56631] 0---0 2017-12-05 10:29:52.304 downimage[938:57199] 1---1 2017-12-05 10:29:52.432 downimage[938:56971] 2---2 2017-12-05 10:29:52.520 downimage[938:56971] 3---3 2017-12-05 10:29:52.576 downimage[938:56631] 4---4 2017-12-05 10:29:52.628 downimage[938:56971] 5---5 2017-12-05 10:29:52.706 downimage[938:56631] 6---6 2017-12-05 10:29:52.764 downimage[938:56971] 7---7 2017-12-05 10:29:52.853 downimage[938:56631] 8---8 2017-12-05 10:29:52.925 downimage[938:56971] 9---9 2017-12-05 10:29:52.926 downimage[938:45252] end
看得出3花费时间为51.488 - 51.178约300多ms
--- ---5花费时间为52.926 - 52.190约700多ms
所以大家还请谨慎使用。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。