iOS 多个异步任务的
http://outofmemory.cn/objective-c/ios-async-task-monitor
在发起网络请求时,我们一般会用异步请求,这里我们以 AFNetWorking
为例:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:@"http://octree.me/" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"Success");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
假如我们要执行多个异步请求,一般可以这么写
NSArray *urlStrings = @[ @"http://octree.me", @"http://google.com", @"http://github.com" ];
for(NSString urlString in urlStrings) {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"Success");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
}
假如我们需要在三个请求都完成或者失败后进行一些处理,但是 manager
发起的请求时异步处理的,也就是说当 manager
调用 GET:parameters:success:failure
会立即返回,当请求成功或失败后才会调用各自的 block
,我们如何才能监控并发的异步事件呢?并且他们的完成顺序以及完成时间都是不确定的。
当然,你可以用一组 bool
值或者其他的标识记录每个任务的完成进度,但是这样的代码不仅丑陋,而且失去了扩展性。
这时候我们就可以用到 dispatch_group
了
dispatch_group
在任务组内的任务都完成的时候通过同步或者异步的方式通知你
dispatch_group
提供了两种通知方式, dispatch_group_wait
和 dispatch_group_notify
dispatch_group_wait
会阻塞当前线程,知道任务都完成时才会继续执行下面的代码
我们可以使用 dispatch_group_wait
这样实现:
NSArray *urlStrings = @[ @"http://octree.me", @"http://google.com", @"http://github.com" ];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_group_t requestGroup = dispatch_group_create();
for(NSString urlString in urlStrings) {
dispatch_group_enter(requestGroup);
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"Success");
dispatch_group_leave(requestGroup);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
dispatch_group_leave(requestGroup);
}];
}
dispatch_group_wait(requestGroup, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
//doSomething;
});
}];
因为 dispatch_group_wait
会阻塞当前线程,所以我们要把他放到后台执行,避免阻塞主线程。通过 dispatch_group_enter
和 dispatch_group_leave
手动通知任务的开始以及结束。 dispatch_group_wait
会阻塞当前线程,知道所有任务完成或者超时才会继续执行下面的代码。
前面我们说过, dispatch_group
提供了两种通知方式,我们已经了解了 dispatch_group_wait
,另一种是 dispath_group_notify
,这种方式相对于前面的显得更为灵活。
dispatch_group_notify
是通过异步的方式通知,所以,不会阻塞线程。于是,我们就可以这样写:
NSArray *urlStrings = @[ @"http://octree.me", @"http://google.com", @"http://github.com" ];
dispatch_group_t requestGroup = dispatch_group_create();
for(NSString urlString in urlStrings) {
dispatch_group_enter(requestGroup);
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"Success");
dispatch_group_leave(requestGroup);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
dispatch_group_leave(requestGroup);
}];
}
dispatch_group_notify(requestGroup, dispatch_get_main_queue(), ^{
//doSomething
});
iOS当多个网络请求完成后执行下一步的方法详解
原文:http://www.jb51.net/article/129829.htm
前言
在开发中,我们很容易遇到这样的需求,需要我们同时做多个网络请求,所有网络请求都完成后才能进行下一步的操作。
网络请求的任务是提交给子线程异步处理了,网络请求这样的任务也就快速执行完毕了,但是网络请求是一个任务,处理收到的网络响应又是一个任务,注意不要把这两个过程混为一谈。
如下载多个图片,下载完了才能展示,今天我们就来研究一下这个问题的解决方案。
解决方法
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
所以大家还请谨慎使用。
iOS判断网络请求超时的方法
http://www.jb51.net/article/98875.htm
iOS判断网络请求超时的方法
本文介绍了iOS判断网络请求超时的方法,代码具体如下:
+ (AFHTTPRequestOperation *)requestOperationWithUrl:(NSString *)url
requetMethod:(NSString *)method
paramData:(NSDictionary *)aParamData
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
success:(successBlock)success
failure:(failureBlock)failure {
AFHTTPRequestSerializer *requestSerializer = [AFJSONRequestSerializer serializer];
NSMutableURLRequest *request;
if (block) {
method = @"POST";
request = [requestSerializer multipartFormRequestWithMethod:method URLString:url parameters:aParamData constructingBodyWithBlock:block error:nil];
}else{
request = [requestSerializer requestWithMethod:method URLString:url parameters:aParamData error:nil];
}
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
AFJSONResponseSerializer *responseSerializer = [AFJSONResponseSerializer serializer];
responseSerializer.removesKeysWithNullValues = YES;
responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
op.responseSerializer = responseSerializer;
__weak AFHTTPRequestOperation *weakOp = op;
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([responseObject[@"code"] integerValue] == 0) {
if (success) {
// success(weakOp, aParamData, responseObject[@"list"]);
success(weakOp, aParamData, responseObject);
}
}else{
NSLog(@"operation error msg = [%@]", responseObject[@"description"]);
if (failure) {
failure(weakOp, aParamData, [self errorWithRet:responseObject]);
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"operation failed = [%@] error = [%@]", operation, error);
if (failure) {
failure(weakOp, aParamData, error);
}
}];
return op;
}
打印 error
Error Domain=NSURLErrorDomain Code=-1001 "请求超时。"
UserInfo={
NSErrorFailingURLStringKey=http://123.56.109.92/refitcar/service.s?sn=, _kCFStreamErrorCodeKey=-2102, NSErrorFailingURLKey=http://123.56.109.92/refitcar/service.s?sn=, NSLocalizedDescription=请求超时。, _kCFStreamErrorDomainKey=4,
NSUnderlyingError=0x167da8e0 {
Error Domain=kCFErrorDomainCFNetwork Code=-1001 "请求超时。"
UserInfo={
_kCFStreamErrorCodeKey=-2102,
NSErrorFailingURLStringKey=http://123.56.109.92/refitcar/service.s?sn=,
NSErrorFailingURLKey=http://123.56.109.92/refitcar/service.s?sn=,
NSLocalizedDescription=请求超时。,
_kCFStreamErrorDomainKey=4
}
}
}
可见:
po error.localizedDescription 请求超时。
po error.code -1001
po error.userInfo
{
NSErrorFailingURLKey = "http://123.56.109.92/refitcar/service.s?sn=";
NSErrorFailingURLStringKey = "http://123.56.109.92/refitcar/service.s?sn=";
NSLocalizedDescription = "\U8bf7\U6c42\U8d85\U65f6\U3002";
NSUnderlyingError = "Error Domain=kCFErrorDomainCFNetwork Code=-1001 \"\U8bf7\U6c42\U8d85\U65f6\U3002\" UserInfo={_kCFStreamErrorCodeKey=-2102, NSErrorFailingURLStringKey=http://123.56.109.92/refitcar/service.s?sn=, NSErrorFailingURLKey=http://123.56.109.92/refitcar/service.s?sn=, NSLocalizedDescription=\U8bf7\U6c42\U8d85\U65f6\U3002, _kCFStreamErrorDomainKey=4}";
"_kCFStreamErrorCodeKey" = "-2102";
"_kCFStreamErrorDomainKey" = 4;
}
所以使用 error.code是否等于 -1001 判断请求超时
iOS中多网络请求的线程安全详解
http://www.jb51.net/article/126462.htm
前言
在iOS 网络编程有一种常见的场景是:我们需要并行处理二个请求并且在都成功后才能进行下一步处理。下面是部分常见的处理方式,但是在使用过程中也很容易出错:
- DispatchGroup:通过 GCD 机制将多个请求放到一个组内,然后通过
DispatchGroup.wait()
和DispatchGroup.notify()
进行成功后的处理。 - OperationQueue:为每一个请求实例化一个 Operation 对象,然后将这些对象添加到 OperationQueue ,并且根据它们之间的依赖关系决定执行顺序。
- 同步 DispatchQueue:通过同步队列和 NSLock 机制避免数据竞争,实现异步多线程中同步安全访问。
- 第三方类库:Futures/Promises 以及响应式编程提供了更高层级的并发抽象。
在多年的实践过程中,我意识到上面这些方法这些方法都存在一定的缺陷。另外,要想完全正确的使用这些类库还是有些困难。
并发编程中的挑战
使用并发的思维思考问题很困难:大多数时候,我们会按照读故事的方式来阅读代码:从第一行到最后一行。如果代码的逻辑不是线性的话,可能会给我们造成一定的理解难度。在单线程环境下,调试和跟踪多个类和框架的程序执行已经是非常头疼的一件事了,多线程环境下这种情况简直不敢想象。
数据竞争问题:在多线程并发环境下,数据读取操作是线程安全的而写操作则是非线程安全。如果发生了多个线程同时对某个内存进行写操作的话,则会发生数据竞争导致潜在数据错误。
理解多线程环境下的动态行为本身就不是一件容易的事,找出导致数据竞争的线程就更为麻烦。虽然我们可以通过互斥锁机制解决数据竞争问题,但是对于可能修改的代码来说互斥锁机制的维护会是一件非常困难的事。
难以测试:并发环境下很多问题并不会在开发过程中显现出来。虽然 Xcode 和 LLVM 提供了Thread Sanitizer 这类工具用于检查这些问题,但是这些问题的调试和跟踪依然存在很大的难度。因为并发环境下除了代码本身的影响外,应用也会受到系统的影响。
处理并发情形的简单方法
考虑到并发编程的复杂性,我们应该如何解决并行的多个请求?
最简单的方式就是避免编写并行代码而是讲多个请求线性的串联在一起:
let session = URLSession.shared
session.dataTask(with: request1) { data, response, error in
// check for errors
// parse the response data
session.dataTask(with: request2) { data, response error in
// check for errors
// parse the response data
// if everything succeeded...
callbackQueue.async {
completionHandler(result1, result2)
}
}.resume()
}.resume()
为了保持代码的简洁,这里忽略了很多的细节处理,例如:错误处理以及请求取消操作。但是这样将并无关联的请求线性排序其实暗藏着一些问题。例如,如果服务端支持 HTTP/2 协议的话,我们就没发利用 HTTP/2 协议中通过同一个链接处理多个请求的特性,而且线性处理也意味着我们没有好好利用处理器的性能。
关于 URLSession 的错误认知
为了避免可能的数据竞争和线程安全问题,我将上面的代码改写为了嵌套请求。也就是说如果将其改为并发请求的话:请求将不能进行嵌套,两个请求可能会对同一块内存进行写操作而数据竞争非常难以重现和调试。
解决改问题的一个可行办法是通过锁机制:在一段时间内只允许一个线程对共享内存进行写操作。锁机制的执行过程也非常简单:请求锁、执行代码、释放锁。当然要想完全正确使用锁机制还是有一些技巧的。
但是根据 URLSession 的文档描述,这里有一个并发请求的更简单解决方案。
init(configuration: URLSessionConfiguration,
delegate: URLSessionDelegate?,
delegateQueue queue: OperationQueue?)
[…]
queue : An operation queue for scheduling the delegate calls and completion handlers. The queue should be a serial queue, in order to ensure the correct ordering of callbacks. If nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.
这意味所有 URLSession 的实例对象包括 URLSession.shared 单例的回调并不会并发执行,除非你明确的传人了一个并发队列给参数 queue 。
URLSession 拓展并发支持
基于上面对 URLSession 的新认知,下面我们对其进行拓展让它支持线程安全的并发请求(完成代码地址)。
enum URLResult {
case response(Data, URLResponse)
case error(Error, Data?, URLResponse?)
}
extension URLSession {
@discardableResult
func get(_ url: URL, completionHandler: @escaping (URLResult) -> Void) -> URLSessionDataTask
}
// Example
let zen = URL(string: "https://api.github.com/zen")!
session.get(zen) { result in
// process the result
}
首先,我们使用了一个简单的 URLResult 枚举来模拟我们可以在 URLSessionDataTask 回调中获得的不同结果。该枚举类型有利于我们简化多个并发请求结果的处理。这里为了文章的简洁并没有贴出 URLSession.get(_:completionHandler:)
方法的完整实现,该方法就是使用 GET 方法请求对应的 URL 并自动执行 resume()
最后将执行结果封装成 URLResult 对象。
@discardableResult
func get(_ left: URL, _ right: URL, completionHandler: @escaping (URLResult, URLResult) -> Void) -> (URLSessionDataTask, URLSessionDataTask) {
}
该段 API 代码接受两个 URL 参数并返回两个 URLSessionDataTask 实例。下面代码是函数实现的第一段:
precondition(delegateQueue.maxConcurrentOperationCount == 1,
"URLSession's delegateQueue must be configured with a maxConcurrentOperationCount of 1.")
因为在实例化 URLSession 对象时依旧可以传入并发的 OperationQueue 对象,所以这里我们需要使用上面这段代码将这种情况排除掉。
var results: (left: URLResult?, right: URLResult?) = (nil, nil)
func continuation() {
guard case let (left?, right?) = results else { return }
completionHandler(left, right)
}
将这段代码继续添加到实现中,其中定义了一个表示返回结果的元组变量 results 。另外,我们还在函数内部定义了另一个工具函数用于检查是否两个请求都已经完成结果处理。
let left = get(left) { result in
results.left = result
continuation()
}
let right = get(right) { result in
results.right = result
continuation()
}
return (left, right)
最后将这段代码追加到实现中,其中我们分别对两个 URL 进行了请求并在请求都完成后一次返回了结果。值得注意的是这里我们通过两次执行 continuation()
来判断请求是否全部完成:
- 第一次执行
continuation()
时因为其中一个请求并未完成结果为 nil 所以回调函数并不会执行。 - 第二次执行的时候两个请求全部完成,执行回调处理。
接下来我们可以通过简单的请求来测试下这段代码:
extension URLResult {
var string: String? {
guard case let .response(data, _) = self,
let string = String(data: data, encoding: .utf8)
else { return nil }
return string
}
}
URLSession.shared.get(zen, zen) { left, right in
guard case let (quote1?, quote2?) = (left.string, right.string)
else { return }
print(quote1, quote2, separator: "\n")
// Approachable is better than simple.
// Practicality beats purity.
}
并行悖论
我发现解决并行问题最简单最优雅的方法就是尽可能的少使用并发编程,而且我们的处理器非常适合执行那些线性代码。但是如果将大的代码块或任务拆分为多个并行执行的小代码块和任务将会让代码变得更加易读和易维护。