欢迎来到GCD深入理解系列教程的第二部分(也是最后一部分)。
在本系列的第一部分中,你已经学到超过你想像的关于并发、线程以及GCD 如何工作的知识。通过在初始化时利用dispatch_once
,你创建了一个线程安全的 PhotoManager
单例,而且你通过使用 dispatch_barrier_async
和dispatch_sync
的组合使得对 Photos
数组的读取和写入都变得线程安全了。
除了上面这些,你还通过利用 dispatch_after
来延迟显示提示信息,以及利用 dispatch_async
将 CPU 密集型任务从 ViewController 的初始化过程中剥离出来异步执行,达到了增强应用的用户体验的目的。
如果你一直跟着第一部分的教程在写代码,那你可以继续你的工程。但如果你没有完成第一部分的工作,或者不想重用你的工程,你可以下载第一部分最终的代码。
那就让我们来更深入地探索 GCD 吧!
纠正过早弹出的提示
你可能已经注意到当你尝试用 Le Internet 选项来添加图片时,一个 UIAlertView
会在图片下载完成之前就弹出,如下如所示:
问题的症结在 PhotoManagers 的 downloadPhotoWithCompletionBlock:
里,它目前的实现如下:
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)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock { __block NSError *error; for (NSInteger i = 0; i < 3; i++) { NSURL *url; switch (i) { case 0: url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString]; break; case 1: url = [NSURL URLWithString:kSuccessKidURLString]; break; case 2: url = [NSURL URLWithString:kLotsOfFacesURLString]; break; default: break; } Photo *photo = [[Photo alloc] initwithURL:url withCompletionBlock:^(UIImage *image, NSError *_error) { if (_error) { error = _error; } }]; [[PhotoManager sharedManager] addPhoto:photo]; } if (completionBlock) { completionBlock(error); } } |
在方法的最后你调用了 completionBlock
——因为此时你假设所有的照片都已下载完成。但很不幸,此时并不能保证所有的下载都已完成。
Photo
类的实例方法用某个 URL 开始下载某个文件并立即返回,但此时下载并未完成。换句话说,当downloadPhotoWithCompletionBlock:
在其末尾调用 completionBlock
时,它就假设了它自己所使用的方法全都是同步的,而且每个方法都完成了它们的工作。
然而,-[Photo initWithURL:withCompletionBlock:]
是异步执行的,会立即返回——所以这种方式行不通。
因此,只有在所有的图像下载任务都调用了它们自己的 Completion Block 之后,downloadPhotoWithCompletionBlock:
才能调用它自己的 completionBlock
。问题是:你该如何监控并发的异步事件?你不知道它们何时完成,而且它们完成的顺序完全是不确定的。
或许你可以写一些比较 Hacky 的代码,用多个布尔值来记录每个下载的完成情况,但这样做就缺失了扩展性,而且说实话,代码会很难看。
幸运的是, 解决这种对多个异步任务的完成进行监控的问题,恰好就是设计 dispatch_group 的目的。
Dispatch Groups(调度组)
Dispatch Group 会在整个组的任务都完成时通知你。这些任务可以是同步的,也可以是异步的,即便在不同的队列也行。而且在整个组的任务都完成时,Dispatch Group 可以用同步的或者异步的方式通知你。因为要监控的任务在不同队列,那就用一个 dispatch_group_t
的实例来记下这些不同的任务。
当组中所有的事件都完成时,GCD 的 API 提供了两种通知方式。
第一种是 dispatch_group_wait
,它会阻塞当前线程,直到组里面所有的任务都完成或者等到某个超时发生。这恰好是你目前所需要的。
打开 PhotoManager.m,用下列实现替换 downloadPhotosWithCompletionBlock:
:
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 41 42 |
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // 1 __block NSError *error; dispatch_group_t downloadGroup = dispatch_group_create(); // 2 for (NSInteger i = 0; i < 3; i++) { NSURL *url; switch (i) { case 0: url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString]; break; case 1: url = [NSURL URLWithString:kSuccessKidURLString]; break; case 2: url = [NSURL URLWithString:kLotsOfFacesURLString]; break; y">[NSURL URLWithString:kLotsOfFacesURLString]; break; ">第一部分中,你已经学到超过你想像的关于并发、线程以及GCD 如何工作的知识。通过在初始化时利用 dispatch_once ,你创建了一个线程安全的 PhotoManager 单例,而且你通过使用 dispatch_barrier_async 和dispatch_sync 的组合使得对 Photos 数组的读取和写入都变得线程安全了。
除了上面这些,你还通过利用 如果你一直跟着第一部分的教程在写代码,那你可以继续你的工程。但如果你没有完成第一部分的工作,或者不想重用你的工程,你可以下载第一部分最终的代码。 那就让我们来更深入地探索 GCD 吧! 纠正过早弹出的提示你可能已经注意到当你尝试用 Le Internet 选项来添加图片时,一个 问题的症结在 PhotoManagers 的
在方法的最后你调用了
然而, 因此,只有在所有的图像下载任务都调用了它们自己的 Completion Block 之后, 或许你可以写一些比较 Hacky 的代码,用多个布尔值来记录每个下载的完成情况,但这样做就缺失了扩展性,而且说实话,代码会很难看。 幸运的是, 解决这种对多个异步任务的完成进行监控的问题,恰好就是设计 dispatch_group 的目的。 Dispatch Groups(调度组)Dispatch Group 会在整个组的任务都完成时通知你。这些任务可以是同步的,也可以是异步的,即便在不同的队列也行。而且在整个组的任务都完成时,Dispatch Group 可以用同步的或者异步的方式通知你。因为要监控的任务在不同队列,那就用一个 当组中所有的事件都完成时,GCD 的 API 提供了两种通知方式。 第一种是 打开 PhotoManager.m,用下列实现替换
|