iOS实战:使用GCD实现多张图片的下载控制

在开发过程中,经常会遇到耗时的操作需要放到子线程中,完成后再返回到主线程中同步UI,以防阻塞主线程。有时候,可能需求是要同时处理多个耗时操作,在处理完所有耗时操作后再同步UI,这时候就可以使用到GCD了。

需求1

同时下载两张图

需要同时下载两张图片,在两张图片都下载完成后,同时返回。

使用group的大致实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)downloadImageSuccess:(void (^)(UIImage *image1, UIImage *image2))success {
dispatch_queue_t global_queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();

__block UIImage *image1;
__block UIImage *image2;
dispatch_group_async(group, global_queue, ^{
NSLog(@"第一张图片开始下载");
sleep(2);
image1 = [UIImage new];
NSLog(@"第一张图片下载完成");
});

dispatch_group_async(group, global_queue, ^{
NSLog(@"第二张图片开始下载");
sleep(3);
image2 = [UIImage new];
NSLog(@"第二张图片下载完成");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"两张图片下载完成");
success(image1, image2);
});
}

需求二

下载超时时间为2秒

设置图片下载超时时间为2秒,如果图片在2秒后没有全部下载完成,那么没完成的图片用nil返回。

代码实现

使用dispatch_group_wait函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- (void)downloadImageSuccess:(void (^)(UIImage *image1, UIImage *image2))success {
dispatch_queue_t global_queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();

__block UIImage *image1;
__block UIImage *image2;
dispatch_group_async(group, global_queue, ^{
NSLog(@"第一张图片开始下载");
sleep(2);
image1 = [UIImage new];
NSLog(@"第一张图片下载完成");
});

dispatch_group_async(group, global_queue, ^{
NSLog(@"第二张图片开始下载");
sleep(4);
image2 = [UIImage new];
NSLog(@"第二张图片下载完成");
});


dispatch_async(global_queue, ^{
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));
dispatch_async(dispatch_get_main_queue(), ^{
success(image1, image2);
});
});
}

2018-12-09 21:56:15.007222+0800 GCDDemo[12279:450516] 第一张图片开始下载
2018-12-09 21:56:15.007222+0800 GCDDemo[12279:450524] 第二张图片开始下载
2018-12-09 21:56:17.011723+0800 GCDDemo[12279:450516] 第一张图片下载完成
2018-12-09 21:56:18.007809+0800 GCDDemo[12279:450477] <UIImage: 0x60000186a450>, {0, 0} (null)
2018-12-09 21:56:19.008913+0800 GCDDemo[12279:450524] 第二张图片下载完成

注意wait一定要放在子线程中,否则就阻塞主线程了。

具体使用GCD完成下载时遇到的问题

问题1:异步任务无法控制

光上面的代码,其实并不能实现多张图片下载的同步功能,因为如果使用系统的封装好的下载的类的话,就只能这样用:

1
2
3
4
5
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:@"image"] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {

}];
[task resume];

如果直接把这段代码扔到dispatch_group_async中,那么只能做到异步开始下载任务,并不能做到同步任务的功能。
类似这样:

1
2
3
4
5
dispatch_group_async(group, global_queue, ^{
dispatch_sync(con_queue, ^{
// TODO
});
});

那么要怎么去做呢?
答案是使用dispatch_group_enterdispatch_group_leave方法。
具体模拟使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    dispatch_queue_t global_queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();

__block UIImage *image1;
__block UIImage *image2;
dispatch_group_enter(group);
dispatch_group_async(group, global_queue, ^{
dispatch_sync(con_queue), ^{
NSLog(@"第一张图片开始下载");
sleep(2);
image1 = [UIImage new];
NSLog(@"第一张图片下载完成");
dispatch_group_leave(group);
});
});

dispatch_group_enter(group);
dispatch_group_async(group, global_queue, ^{
dispatch_sync(con_queue), ^{
NSLog(@"第二张图片开始下载");
sleep(4);
image2 = [UIImage new];
NSLog(@"第二张图片下载完成");
dispatch_group_leave(group);
});
});

dispatch_async(global_queue, ^{
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));
dispatch_async(dispatch_get_main_queue(), ^{
success(image1, image2);
});
});


2018-12-09 22:23:36.918395+0800 GCDDemo[12558:492481] 第一张图片开始下载
2018-12-09 22:23:36.918395+0800 GCDDemo[12558:492483] 第二张图片开始下载
2018-12-09 22:23:38.923074+0800 GCDDemo[12558:492481] 第一张图片下载完成
2018-12-09 22:23:39.919640+0800 GCDDemo[12558:492349] <UIImage: 0x600003ffb100>, {0, 0} (null)
2018-12-09 22:23:40.923513+0800 GCDDemo[12558:492483] 第二张图片下载完成

问题2:如何取消图片下载任务

其实使用SDWebImage的SDWebImageDownloader类可以直接取消下载操作。
同时我看到NSURLSessionDownloadTask类中也有cancel方法,但是我没试。。。

请我吃一块黄金鸡块