开发需求:
1、原型是多个网络请求不做任何处理
2、多个网络请求,需要所有网络请求都完成后才能进行下一步的操作。如下载多个图片,下载完了才能展示。
3、多个网络请求,在2情况下还要求所有的网络请求必须回调也按顺序回调。
实测
首先在viewDidLoad创建一个按钮 点击的时候做网络请求
- (void)viewDidLoad {
[super viewDidLoad];
//1.无处理
UIButton *Btn = [UIButton buttonWithType:UIButtonTypeCustom];
Btn.frame = CGRectMake(100, 100, 100, 40);
Btn.backgroundColor = [UIColor grayColor];
[Btn setTitle:@"testBtn" forState:UIControlStateNormal];
[Btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:Btn];
}
1、多个网络请求不做任何处理
-(void)btnClick{
NSString *str = @"";
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");
}
运行,控制台输出:
2019-02-21 16:30:53.107132+0800 demo_test[79076:6322778] end
2019-02-21 16:30:53.932530+0800 demo_test[79076:6325473] 6---6
2019-02-21 16:30:53.932687+0800 demo_test[79076:6325484] 7---7
2019-02-21 16:30:53.934681+0800 demo_test[79076:6325484] 8---8
2019-02-21 16:30:53.935822+0800 demo_test[79076:6325484] 0---0
2019-02-21 16:30:53.936317+0800 demo_test[79076:6325562] 2---2
2019-02-21 16:30:53.936517+0800 demo_test[79076:6325562] 9---9
2019-02-21 16:30:53.937033+0800 demo_test[79076:6325562] 5---5
2019-02-21 16:30:53.937587+0800 demo_test[79076:6325556] 4---4
2019-02-21 16:30:53.938096+0800 demo_test[79076:6325556] 1---1
2019-02-21 16:30:53.938598+0800 demo_test[79076:6325562] 3---3
很明显,无任何处理情况下,end最先被打印出来,由于网络请求的异步回调,然后各个网络请求的回调顺序是无序的。
2、多个网络请求,需要所有网络请求都完成后才能进行下一步的操作。
(1)使用GCD的dispatch_group_t
-(void)btnClick{
NSString *str = @"";
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");
});
}
运行,控制台输出:
2019-02-21 16:46:40.643445+0800 demo_test[79245:6369355] 6---6
2019-02-21 16:46:40.643764+0800 demo_test[79245:6369362] 7---7
2019-02-21 16:46:40.643868+0800 demo_test[79245:6369315] 8---8
2019-02-21 16:46:40.683800+0800 demo_test[79245:6369356] 9---9
2019-02-21 16:46:40.687189+0800 demo_test[79245:6369315] 2---2
2019-02-21 16:46:40.723518+0800 demo_test[79245:6369315] 4---4
2019-02-21 16:46:40.728108+0800 demo_test[79245:6368107] 1---1
2019-02-21 16:46:40.728782+0800 demo_test[79245:6368107] 5---5
2019-02-21 16:46:40.738189+0800 demo_test[79245:6369356] 3---3
2019-02-21 16:46:40.774204+0800 demo_test[79245:6369362] 0---0
2019-02-21 16:46:40.774317+0800 demo_test[79245:6367926] end
可以看出,end是在所有网络请求之后才输出出来,这也是符合了我们的需求。
代码中我们只添加了四行代码
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。
(2)使用GCD的信号量dispatch_semaphore_t
-(void)btnClick{
NSString *str = @"";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
__weak typeof(self) weakSelf = self;
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);
weakSelf.count++;
if (weakSelf.count==10) {
dispatch_semaphore_signal(sem);
weakSelf.count = 0;
}
}];
[task resume];
}
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"end");
});
}
运行,控制台输出:
2019-02-21 17:19:44.727240+0800 demo_test[79572:6438103] 6---6
2019-02-21 17:19:44.727483+0800 demo_test[79572:6438103] 7---7
2019-02-21 17:19:44.727572+0800 demo_test[79572:6438103] 8---8
2019-02-21 17:19:44.769205+0800 demo_test[79572:6438053] 9---9
2019-02-21 17:19:44.770698+0800 demo_test[79572:6438098] 4---4
2019-02-21 17:19:44.771508+0800 demo_test[79572:6438103] 1---1
2019-02-21 17:19:44.812112+0800 demo_test[79572:6438098] 2---2
2019-02-21 17:19:44.812919+0800 demo_test[79572:6434100] 5---5
2019-02-21 17:19:44.813794+0800 demo_test[79572:6438098] 3---3
2019-02-21 17:19:44.854245+0800 demo_test[79572:6438053] 0---0
2019-02-21 17:19:44.854978+0800 demo_test[79572:6433948] 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次之后再发信号量,使后面程序继续运行)。
3、多个网络请求,在2情况下还要求所有的网络请求必须回调也按顺序回调。
(1)还是使用信号量semaphore完成需求
-(void)btnClick{
NSString *str = @"";
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");
});
}
运行,控制台输出:
2019-02-21 17:29:15.208386+0800 demo_test[79679:6474505] 0---0
2019-02-21 17:29:15.307529+0800 demo_test[79679:6474505] 1---1
2019-02-21 17:29:15.378151+0800 demo_test[79679:6474557] 2---2
2019-02-21 17:29:15.447956+0800 demo_test[79679:6474505] 3---3
2019-02-21 17:29:15.554771+0800 demo_test[79679:6474557] 4---4
2019-02-21 17:29:15.639260+0800 demo_test[79679:6474505] 5---5
2019-02-21 17:29:15.713266+0800 demo_test[79679:6474557] 6---6
2019-02-21 17:29:15.796437+0800 demo_test[79679:6470017] 7---7
2019-02-21 17:29:15.878718+0800 demo_test[79679:6474557] 8---8
2019-02-21 17:29:15.965581+0800 demo_test[79679:6474505] 9---9
2019-02-21 17:29:15.966260+0800 demo_test[79679:6469469] end
但这样做一个问题,我们使用这种方式,可以明显感觉出整个过程需要花费的时间大大增加了,不像我们 2(2) 中同时(几乎)开启任务等待完成回调,这里是一个网络请求发出,等待,完成后发出第二个网络请求,等待,完成后再发出第三个,这样我们等待的时间是10个网络请求每一个回调时间的和,在时间上大大增加了消耗,而且对于dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER),它是会阻塞线程的,我们如果需要在网络请求完成后修改UI,那这种方式会影响我们的界面交互,接下来我们对比一下两者时间消耗:
2(2)
2019-02-21 17:19:44.727240+0800 demo_test[79572:6438103] 6---6
2019-02-21 17:19:44.727483+0800 demo_test[79572:6438103] 7---7
2019-02-21 17:19:44.727572+0800 demo_test[79572:6438103] 8---8
2019-02-21 17:19:44.769205+0800 demo_test[79572:6438053] 9---9
2019-02-21 17:19:44.770698+0800 demo_test[79572:6438098] 4---4
2019-02-21 17:19:44.771508+0800 demo_test[79572:6438103] 1---1
2019-02-21 17:19:44.812112+0800 demo_test[79572:6438098] 2---2
2019-02-21 17:19:44.812919+0800 demo_test[79572:6434100] 5---5
2019-02-21 17:19:44.813794+0800 demo_test[79572:6438098] 3---3
2019-02-21 17:19:44.854245+0800 demo_test[79572:6438053] 0---0
2019-02-21 17:19:44.854978+0800 demo_test[79572:6433948] end
3(1)
2019-02-21 17:29:15.208386+0800 demo_test[79679:6474505] 0---0
2019-02-21 17:29:15.307529+0800 demo_test[79679:6474505] 1---1
2019-02-21 17:29:15.378151+0800 demo_test[79679:6474557] 2---2
2019-02-21 17:29:15.447956+0800 demo_test[79679:6474505] 3---3
2019-02-21 17:29:15.554771+0800 demo_test[79679:6474557] 4---4
2019-02-21 17:29:15.639260+0800 demo_test[79679:6474505] 5---5
2019-02-21 17:29:15.713266+0800 demo_test[79679:6474557] 6---6
2019-02-21 17:29:15.796437+0800 demo_test[79679:6470017] 7---7
2019-02-21 17:29:15.878718+0800 demo_test[79679:6474557] 8---8
2019-02-21 17:29:15.965581+0800 demo_test[79679:6474505] 9---9
2019-02-21 17:29:15.966260+0800 demo_test[79679:6469469] end
看得出2(2)花费时间为44.854 - 44.727约100多ms
--- ---3(1)花费时间为15.966 - 15.208约700多ms
3(2)用NSConditionLock条件锁(重点推荐)
-(void)btnClick{
NSString *str = @"";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
self.lock = [[NSConditionLock alloc] initWithCondition:0];
dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
for (int i=0; i<10; i++) {
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
dispatch_async(queue, ^{
[self.lock lockWhenCondition:i];
NSLog(@"%d---%d",i,i);
[self.lock unlockWithCondition:i+1];
});
}];
[task resume];
}
}
运行,控制台输出:
2019-02-21 17:39:27.852553+0800 demo_test[79807:6515122] 0---0
2019-02-21 17:39:27.852737+0800 demo_test[79807:6515128] 1---1
2019-02-21 17:39:27.893596+0800 demo_test[79807:6515129] 2---2
2019-02-21 17:39:27.895559+0800 demo_test[79807:6515128] 3---3
2019-02-21 17:39:27.895696+0800 demo_test[79807:6515122] 4---4
2019-02-21 17:39:27.895843+0800 demo_test[79807:6514898] 5---5
2019-02-21 17:39:27.895913+0800 demo_test[79807:6514899] 6---6
2019-02-21 17:39:27.895984+0800 demo_test[79807:6514901] 7---7
2019-02-21 17:39:27.896057+0800 demo_test[79807:6514900] 8---8
2019-02-21 17:39:27.896157+0800 demo_test[79807:6515121] 9---9
可以看出所用时间还不到100ms,在这个方法中,我们使用了
//初始化一个NSConditionLock对象,锁条件为0
self.lock = [[NSConditionLock alloc] initWithCondition:0];
//当i满足锁条件时加锁
[self.lock lockWhenCondition:i];
//解锁,并重置锁的条件为i+1
[self.lock unlockWithCondition:i+1];
添加代码理解:
1、初始化了一个锁,并添加锁条件为0
2、当i满足锁条件的时候就进行加锁
3、进行解锁,并重置锁的条件为i+1