深入理解 GCD(二)

619 查看

欢迎来到GCD深入理解系列教程的第二部分(也是最后一部分)。

在本系列的第一部分中,你已经学到超过你想像的关于并发、线程以及GCD 如何工作的知识。通过在初始化时利用dispatch_once,你创建了一个线程安全的 PhotoManager 单例,而且你通过使用 dispatch_barrier_async 和dispatch_sync 的组合使得对 Photos 数组的读取和写入都变得线程安全了。

除了上面这些,你还通过利用 dispatch_after 来延迟显示提示信息,以及利用 dispatch_async 将 CPU 密集型任务从 ViewController 的初始化过程中剥离出来异步执行,达到了增强应用的用户体验的目的。

如果你一直跟着第一部分的教程在写代码,那你可以继续你的工程。但如果你没有完成第一部分的工作,或者不想重用你的工程,你可以下载第一部分最终的代码

那就让我们来更深入地探索 GCD 吧!

纠正过早弹出的提示

你可能已经注意到当你尝试用 Le Internet 选项来添加图片时,一个 UIAlertView 会在图片下载完成之前就弹出,如下如所示:

问题的症结在 PhotoManagers 的 downloadPhotoWithCompletionBlock: 里,它目前的实现如下:

在方法的最后你调用了 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: