iOS当多个网络请求完成后执行下一步的方法详解
前言
在开发中,我们很容易遇到这样的需求,需要我们同时做多个网络请求,所有网络请求都完成后才能进行下一步的操作。
网络请求的任务是提交给子线程异步处理了,网络请求这样的任务也就快速执行完毕了,但是网络请求是一个任务,处理收到的网络响应又是一个任务,注意不要把这两个过程混为一谈。
如下载多个图片,下载完了才能展示,今天我们就来研究一下这个问题的解决方案。
解决方法
1.首先,我们创建一个项目,然后做一般性的做法,不做任何处理去连续请求一个接口10次:
先在viewDidLoad中创建第一种情况.
//1.无处理 UIButton*Btn1=[UIButtonbuttonWithType:UIButtonTypeCustom]; Btn1.frame=CGRectMake(100,100,100,40); Btn1.backgroundColor=[UIColorgrayColor]; [Btn1setTitle:@"noConduct"forState:UIControlStateNormal]; [Btn1addTarget:selfaction:@selector(Btn1)forControlEvents:UIControlEventTouchUpInside]; [self.viewaddSubview:Btn1];
实现第一种情况的方法
//1.noConduct -(void)Btn1{ NSString*str=@"http://www.jianshu.com/p/6930f335adba"; NSURL*url=[NSURLURLWithString:str]; NSURLRequest*request=[NSURLRequestrequestWithURL:url]; NSURLSession*session=[NSURLSessionsharedSession]; for(inti=0;i<10;i++){ NSURLSessionDataTask*task=[sessiondataTaskWithRequest:requestcompletionHandler:^(NSData*_Nullabledata,NSURLResponse*_Nullableresponse,NSError*_Nullableerror){ NSLog(@"%d---%d",i,i); }]; [taskresume]; } NSLog(@"end"); }
运行,看看我们的控制台输出:
2017-12-0417:10:10.503DownImage[3289:261033]end 2017-12-0417:10:10.676DownImage[3289:261080]0---0 2017-12-0417:10:10.704DownImage[3289:261080]1---1 2017-12-0417:10:10.754DownImage[3289:261096]4---4 2017-12-0417:10:10.760DownImage[3289:261080]2---2 2017-12-0417:10:10.800DownImage[3289:261096]5---5 2017-12-0417:10:10.840DownImage[3289:261080]7---7 2017-12-0417:10:10.844DownImage[3289:261082]6---6 2017-12-0417:10:10.846DownImage[3289:261096]3---3 2017-12-0417:10:10.888DownImage[3289:261096]8---8 2017-12-0417:10:10.945DownImage[3289:261080]9---9
很明显,无任何处理情况下,end最先被打印出来,由于网络请求的异步回调,然后各个网络请求的回调顺序是无序的。
2.使用GCD的dispatch_group_t
viewDidLoad里:
//2.group UIButton*Btn2=[UIButtonbuttonWithType:UIButtonTypeCustom]; Btn2.frame=CGRectMake(100,200,100,40); Btn2.backgroundColor=[UIColorgrayColor]; [Btn2setTitle:@"group--"forState:UIControlStateNormal]; [Btn2addTarget:selfaction:@selector(Btn2)forControlEvents:UIControlEventTouchUpInside]; [self.viewaddSubview:Btn2];
实现:
//2.group-- -(void)Btn2{ NSString*str=@"http://www.jianshu.com/p/6930f335adba"; NSURL*url=[NSURLURLWithString:str]; NSURLRequest*request=[NSURLRequestrequestWithURL:url]; NSURLSession*session=[NSURLSessionsharedSession]; dispatch_group_tdownloadGroup=dispatch_group_create(); for(inti=0;i<10;i++){ dispatch_group_enter(downloadGroup); NSURLSessionDataTask*task=[sessiondataTaskWithRequest:requestcompletionHandler:^(NSData*_Nullabledata,NSURLResponse*_Nullableresponse,NSError*_Nullableerror){ NSLog(@"%d---%d",i,i); dispatch_group_leave(downloadGroup); }]; [taskresume]; } dispatch_group_notify(downloadGroup,dispatch_get_main_queue(),^{ NSLog(@"end"); }); }
运行看看控制台输出:
2017-12-0417:14:46.984DownImage[3289:265374]2---2 2017-12-0417:14:46.987DownImage[3289:265370]1---1 2017-12-0417:14:47.052DownImage[3289:265383]5---5 2017-12-0417:14:47.065DownImage[3289:265370]4---4 2017-12-0417:14:47.111DownImage[3289:265379]3---3 2017-12-0417:14:47.121DownImage[3289:265383]6---6 2017-12-0417:14:47.169DownImage[3289:265383]7---7 2017-12-0417:14:47.192DownImage[3289:265370]9---9 2017-12-0417:14:47.321DownImage[3289:265383]8---8 2017-12-0417:14:47.747DownImage[3289:265374]0---0 2017-12-0417:14:47.747DownImage[3289:261033]end
2017-12-0417:15:14.576DownImage[3289:265942]3---3 2017-12-0417:15:14.626DownImage[3289:265936]2---2 2017-12-0417:15:14.647DownImage[3289:265944]4---4 2017-12-0417:15:14.648DownImage[3289:265936]0---0 2017-12-0417:15:14.657DownImage[3289:265943]1---1 2017-12-0417:15:14.709DownImage[3289:265944]5---5 2017-12-0417:15:14.728DownImage[3289:265944]6---6 2017-12-0417:15:14.734DownImage[3289:265944]7---7 2017-12-0417:15:14.738DownImage[3289:265943]8---8 2017-12-0417:15:14.816DownImage[3289:265944]9---9 2017-12-0417:15:14.816DownImage[3289:261033]end
从上两次输出可以看出,end确实是在所有网络请求之后才输出出来,这也是符合了我们的需求。
代码中我们只添加了4行代码
dispatch_group_tdownloadGroup=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=[UIButtonbuttonWithType:UIButtonTypeCustom]; Btn3.frame=CGRectMake(100,300,100,40); Btn3.backgroundColor=[UIColorgrayColor]; [Btn3setTitle:@"semaphore"forState:UIControlStateNormal]; [Btn3addTarget:selfaction:@selector(Btn3)forControlEvents:UIControlEventTouchUpInside]; [self.viewaddSubview:Btn3];
//3.semaphore-- -(void)Btn3{ NSString*str=@"http://www.jianshu.com/p/6930f335adba"; NSURL*url=[NSURLURLWithString:str]; NSURLRequest*request=[NSURLRequestrequestWithURL:url]; NSURLSession*session=[NSURLSessionsharedSession]; dispatch_semaphore_tsem=dispatch_semaphore_create(0); for(inti=0;i<10;i++){ NSURLSessionDataTask*task=[sessiondataTaskWithRequest:requestcompletionHandler:^(NSData*_Nullabledata,NSURLResponse*_Nullableresponse,NSError*_Nullableerror){ NSLog(@"%d---%d",i,i); count++; if(count==10){ dispatch_semaphore_signal(sem); count=0; } }]; [taskresume]; } dispatch_semaphore_wait(sem,DISPATCH_TIME_FOREVER); dispatch_async(dispatch_get_main_queue(),^{ NSLog(@"end"); }); }
运行看看控制台输出:
2017-12-0417:36:49.098DownImage[3428:283651]2---2 2017-12-0417:36:49.144DownImage[3428:284210]0---0 2017-12-0417:36:49.152DownImage[3428:284213]3---3 2017-12-0417:36:49.158DownImage[3428:283651]1---1 2017-12-0417:36:49.167DownImage[3428:284210]4---4 2017-12-0417:36:49.235DownImage[3428:284213]8---8 2017-12-0417:36:49.249DownImage[3428:283651]5---5 2017-12-0417:36:49.252DownImage[3428:283651]7---7 2017-12-0417:36:49.324DownImage[3428:283651]9---9 2017-12-0417:36:49.468DownImage[3428:284214]6---6 2017-12-0417:36:49.469DownImage[3428:283554]end
2017-12-0417:37:11.554DownImage[3428:284747]0---0 2017-12-0417:37:11.555DownImage[3428:284733]1---1 2017-12-0417:37:11.627DownImage[3428:284748]5---5 2017-12-0417:37:11.661DownImage[3428:284748]2---2 2017-12-0417:37:11.688DownImage[3428:284747]4---4 2017-12-0417:37:11.709DownImage[3428:284747]6---6 2017-12-0417:37:11.770DownImage[3428:284733]7---7 2017-12-0417:37:11.774DownImage[3428:284733]8---8 2017-12-0417:37:11.824DownImage[3428:284747]9---9 2017-12-0417:37:11.899DownImage[3428:284733]3---3 2017-12-0417:37:11.900DownImage[3428:283554]end
从输出可以看出,这样的方法也是满足我们的需求的,在这个方法中,我们使用了
dispatch_semaphore_tsem=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这种方式的。
对于这个需求我也是根据自己最近做的项目来提的,因为网络请求回调的异步性,我们虽可以控制网络请求的顺序执行,却不能控制它的完成回调顺序。这就有点伤了,目前我项目是找到了解决方案,但这个问题还没有找到解决办法,提出来跟大家讨论一下。(请忽略网络请求执行,回调,在回调里请求下一个接口的办法,讨论还有没有别的方法,最好showthecode).
最后,贴点NSOperation的代码,为了解决新需求所写,由于网络请求回调异步性不能满足需求,但若不是网络请求等异步回调的方式,这样的做法是可以的,大家可以试试.
//4.NSOperation UIButton*Btn4=[UIButtonbuttonWithType:UIButtonTypeCustom]; Btn4.frame=CGRectMake(100,400,100,40); Btn4.backgroundColor=[UIColorgrayColor]; [Btn4setTitle:@"NSOperation"forState:UIControlStateNormal]; [Btn4addTarget:selfaction:@selector(Btn4)forControlEvents:UIControlEventTouchUpInside]; [self.viewaddSubview:Btn4];
//4.NSOperation -(void)Btn4{ NSString*str=@"http://www.jianshu.com/p/6930f335adba"; NSURL*url=[NSURLURLWithString:str]; NSURLRequest*request=[NSURLRequestrequestWithURL:url]; NSURLSession*session=[NSURLSessionsharedSession]; NSMutableArray*operationArr=[[NSMutableArrayalloc]init]; for(inti=0;i<10;i++){ NSBlockOperation*operation=[NSBlockOperationblockOperationWithBlock:^{ NSURLSessionDataTask*task=[sessiondataTaskWithRequest:requestcompletionHandler:^(NSData*_Nullabledata,NSURLResponse*_Nullableresponse,NSError*_Nullableerror){ NSLog(@"%d---%d",i,i); }]; [taskresume]; //非网络请求 NSLog(@"noRequest-%d",i); }]; [operationArraddObject:operation]; if(i>0){ NSBlockOperation*operation1=operationArr[i-1]; NSBlockOperation*operation2=operationArr[i]; [operation2addDependency:operation1]; } } NSOperationQueue*queue=[[NSOperationQueuealloc]init]; [queueaddOperations:operationArrwaitUntilFinished:NO];//YES会阻塞当前线程 #warning-绝对不要在应用主线程中等待一个Operation,只能在第二或次要线程中等待。阻塞主线程将导致应用无法响应用户事件,应用也将表现为无响应。 }
运行结果:
2017-12-0418:03:10.224DownImage[3584:304363]noRequest-0 2017-12-0418:03:10.226DownImage[3584:304362]noRequest-1 2017-12-0418:03:10.226DownImage[3584:304363]noRequest-2 2017-12-0418:03:10.231DownImage[3584:304363]noRequest-3 2017-12-0418:03:10.232DownImage[3584:304362]noRequest-4 2017-12-0418:03:10.233DownImage[3584:304362]noRequest-5 2017-12-0418:03:10.233DownImage[3584:304363]noRequest-6 2017-12-0418:03:10.234DownImage[3584:304363]noRequest-7 2017-12-0418:03:10.235DownImage[3584:304363]noRequest-8 2017-12-0418:03:10.236DownImage[3584:304363]noRequest-9 2017-12-0418:03:10.408DownImage[3584:304597]2---2 2017-12-0418:03:10.408DownImage[3584:304597]0---0 2017-12-0418:03:10.409DownImage[3584:304597]1---1 2017-12-0418:03:10.461DownImage[3584:304597]5---5 2017-12-0418:03:10.476DownImage[3584:304363]4---4 2017-12-0418:03:10.477DownImage[3584:304365]6---6 2017-12-0418:03:10.518DownImage[3584:304365]7---7 2017-12-0418:03:10.537DownImage[3584:304596]8---8 2017-12-0418:03:10.547DownImage[3584:304362]9---9 2017-12-0418:03:11.837DownImage[3584:304362]3---3
2017-12-0418:04:27.699DownImage[3584:306401]noRequest-0 2017-12-0418:04:27.700DownImage[3584:306405]noRequest-1 2017-12-0418:04:27.701DownImage[3584:306401]noRequest-2 2017-12-0418:04:27.701DownImage[3584:306405]noRequest-3 2017-12-0418:04:27.702DownImage[3584:306401]noRequest-4 2017-12-0418:04:27.702DownImage[3584:306405]noRequest-5 2017-12-0418:04:27.703DownImage[3584:306401]noRequest-6 2017-12-0418:04:27.703DownImage[3584:306401]noRequest-7 2017-12-0418:04:27.704DownImage[3584:306401]noRequest-8 2017-12-0418:04:27.704DownImage[3584:306401]noRequest-9 2017-12-0418:04:27.772DownImage[3584:306397]2---2 2017-12-0418:04:27.779DownImage[3584:306401]0---0 2017-12-0418:04:27.782DownImage[3584:306409]1---1 2017-12-0418:04:27.800DownImage[3584:306405]3---3 2017-12-0418:04:27.851DownImage[3584:306401]6---6 2017-12-0418:04:27.855DownImage[3584:306397]5---5 2017-12-0418:04:27.915DownImage[3584:306397]7---7 2017-12-0418:04:27.951DownImage[3584:306397]9---9 2017-12-0418:04:27.953DownImage[3584:306405]8---8 2017-12-0418:04:28.476DownImage[3584:306409]4---4
5.还是使用信号量semaphore完成4的需求
//5.semaphore---order UIButton*Btn5=[UIButtonbuttonWithType:UIButtonTypeCustom]; Btn5.frame=CGRectMake(100,500,100,40); Btn5.backgroundColor=[UIColorgrayColor]; [Btn5setTitle:@"order"forState:UIControlStateNormal]; [Btn5addTarget:selfaction:@selector(Btn5)forControlEvents:UIControlEventTouchUpInside]; [self.viewaddSubview:Btn5];
//5.semaphore--order -(void)Btn5{ NSString*str=@"http://www.jianshu.com/p/6930f335adba"; NSURL*url=[NSURLURLWithString:str]; NSURLRequest*request=[NSURLRequestrequestWithURL:url]; NSURLSession*session=[NSURLSessionsharedSession]; dispatch_semaphore_tsem=dispatch_semaphore_create(0); for(inti=0;i<10;i++){ NSURLSessionDataTask*task=[sessiondataTaskWithRequest:requestcompletionHandler:^(NSData*_Nullabledata,NSURLResponse*_Nullableresponse,NSError*_Nullableerror){ NSLog(@"%d---%d",i,i); dispatch_semaphore_signal(sem); }]; [taskresume]; dispatch_semaphore_wait(sem,DISPATCH_TIME_FOREVER); } dispatch_async(dispatch_get_main_queue(),^{ NSLog(@"end"); }); }
我们看看运行结果:
2017-12-0510:17:28.175DownImage[938:51296]0---0 2017-12-0510:17:28.331DownImage[938:51289]1---1 2017-12-0510:17:28.506DownImage[938:51289]2---2 2017-12-0510:17:28.563DownImage[938:51289]3---3 2017-12-0510:17:28.662DownImage[938:51289]4---4 2017-12-0510:17:28.733DownImage[938:51296]5---5 2017-12-0510:17:28.792DownImage[938:51296]6---6 2017-12-0510:17:28.856DownImage[938:51286]7---7 2017-12-0510:17:29.574DownImage[938:51289]8---8 2017-12-0510:17:29.652DownImage[938:51286]9---9 2017-12-0510:17:29.653DownImage[938:45252]end
2017-12-0510:17:46.341DownImage[938:51608]0---0 2017-12-0510:17:47.967DownImage[938:51607]1---1 2017-12-0510:17:48.038DownImage[938:51603]2---2 2017-12-0510:17:48.132DownImage[938:51603]3---3 2017-12-0510:17:48.421DownImage[938:51608]4---4 2017-12-0510:17:48.537DownImage[938:51289]5---5 2017-12-0510:17:48.646DownImage[938:51289]6---6 2017-12-0510:17:48.939DownImage[938:51289]7---7 2017-12-0510:17:50.537DownImage[938:51607]8---8 2017-12-0510:17:50.615DownImage[938:51289]9---9 2017-12-0510:17:50.616DownImage[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-0510:29:51.178DownImage[938:56971]2---2 2017-12-0510:29:51.193DownImage[938:57200]0---0 2017-12-0510:29:51.202DownImage[938:56631]3---3 2017-12-0510:29:51.248DownImage[938:56971]1---1 2017-12-0510:29:51.262DownImage[938:56971]5---5 2017-12-0510:29:51.291DownImage[938:56631]6---6 2017-12-0510:29:51.375DownImage[938:56631]7---7 2017-12-0510:29:51.384DownImage[938:56631]4---4 2017-12-0510:29:51.434DownImage[938:56971]8---8 2017-12-0510:29:51.487DownImage[938:57199]9---9 2017-12-0510:29:51.488DownImage[938:45252]end 5-------------5----------5------- 2017-12-0510:29:52.190DownImage[938:56631]0---0 2017-12-0510:29:52.304DownImage[938:57199]1---1 2017-12-0510:29:52.432DownImage[938:56971]2---2 2017-12-0510:29:52.520DownImage[938:56971]3---3 2017-12-0510:29:52.576DownImage[938:56631]4---4 2017-12-0510:29:52.628DownImage[938:56971]5---5 2017-12-0510:29:52.706DownImage[938:56631]6---6 2017-12-0510:29:52.764DownImage[938:56971]7---7 2017-12-0510:29:52.853DownImage[938:56631]8---8 2017-12-0510:29:52.925DownImage[938:56971]9---9 2017-12-0510:29:52.926DownImage[938:45252]end
看得出3花费时间为51.488-51.178约300多ms
------5花费时间为52.926-52.190约700多ms
所以大家还请谨慎使用。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。