在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
尽管Grand Central Dispatch(GCD)已经存在一段时间了,但并非每个人都知道怎么使用它。这是情有可原的,因为并发很棘手,而且GCD本身基于C的API在Swift世界中很刺眼。 在这两篇教程中,你会学到GCD的来龙去脉。第一部分解释了GCD可以做什么和几个基本功能。第二部分,你会学到一些GCD所提供的进阶功能。 起步 libdispatch是Apple所提供的在IOS和OS X上进行并发编程的库,而GCD正是它市场化的名字。GCD有如下优点: – GCD可以将计算复杂的任务放到后台执行,从而提升app的响应性能 – GCD提供了比锁和线程更简单的并发模型,帮助开发者避免并发的bug。 为了理解GCD,你需要了解一些线程和并发的概念。这些概念可能很含糊并且细微,所以先简要回顾一下。 串行 vs 并发 这两个词用来描述任务的执行顺序。串行在同一时间点总是单独执行一个任务,而并发可以同时执行多个任务。 任务 在本教程中,你可以把任务当做一个闭包(closure)。实际上,你可以将GCD和函数指针一起使用,但是一般很少这样使用。闭包更简单! 不记得Swift中的闭包?闭包是自含的,可保存传递并被调用的代码块。当调用的时候,他们的用法很像函数,可以有参数和返回值。除此之外,闭包可以“捕获”外部的变量,也就是说,它可以看到并记住它自身被定义时的作用域变量。 Swift中的闭包和OC中的块(block)类似甚至于他们几乎就是可交换使用的。唯一的限制在于OC中不能使用Swift独有的特性,比如元组(tuple)。但OC中的块可以安全的替换成Swift中的闭包。 同步 vs 异步 这两个词描述的是函数何时将控制权返回给调用者,以及在返回时任务的完成情况。 同步函数只有在任务完成后才会返回。 异步函数会立即返回,不会等待任务完成。因此异步函数不会阻塞当前线程。 注意:当你读到同步函数阻塞(block)当前进程或者函数是阻塞(blocking)函数时,不要困惑!动词阻塞(block)描述的是函数对当前线程的影响,和块(block)没有关系。同时记住GCD文档中有关OC的block可以跟Swift的闭包互换。 临界区(Critical Section) 这是一段不能并发执行的代码,也就是说两个线程不可以同时执行它。这通常是因为这段代码会修改共享的资源。否则,并发的进程同时修改同一个变量会导致错误。 竞态条件 当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。竞态条件可能产生在代码检查时不易被发现的不可预期行为。 死锁 两个或更多的线程因等待彼此完成而陷入的困境称为死锁。第一个线程无法完成因为它在等待第二个线程完成。但是第二个线程也无法完成因为它在等待第一个线程完成。 线程安全 线程安全的代码是可以被多个线程或并发任务安全调用的,他不会造成任何问题(数据错误,崩溃等)。非线程安全的代码在同一时间只能单独执行。一段线程安全的代码如let a = ["thread-safe"]。由于数组是只读的,它可以被多个线程同时使用而不会引发问题。另一方面,var a = ["thread-unsafe"]是可变数组。这意味着它不是线程安全的,因为多个线程可以同时获取并修改这个数组,会得到不可预料的结果。非线程安全的变量和可变的数据结构在同一时刻应该只能被一个线程获取。 上下文切换 上下文切换是在进程中切换不同线程时保存和恢复程序执行状态的过程。这一过程在编写多任务app时相当常见,但是会造成一些额外开支。 并发 vs 并行 并发和并行经常会被同时提起,所以值得通过简短的解释来区分彼此。 并发代码中的单独部分可以同时执行。然而,这要由系统来决定并发怎样发生或是否发生。
多核设备通过并行来同时执行多个线程;然而,在单核设备中,必须要通过上下文切换来运行另一个线程或进程。这一过程通常发生的很快以至于给人并行的假象。如下图所示:
尽管你可能在GCD之下编写并发执行的代码,但仍由GCD来决定并行的需求有多大。 深层次的观点是并发实际上是关乎结构的。当你编写GCD代码时,你组织你的代码来揭示出可以同时运行的工作,以及不可以同时运行的。如果你想深入了解这个主题,猛击Rob Pike。 队列 GCD提供了调度队列(dispatch queues)来处理提交的任务;这些队列管理着你向GCD提交的任务并且以先进先出(FIFO)的顺序来执行任务。这保证了第一个加入队列的任务第一个被执行,第二个加入的任务第二个开始执行,以此类推。 所有调度队列都是线程安全的从而让你可以同时在多个线程中使用它们。当你明白了调度队列如何为你的代码提供了线程安全性时,GCD的优点就很明显了。关键是选择正确的调度队列种类和正确的调度函数(dispatching function)来提交你的任务。 顺序队列
顺序队列中的任务同一时间只执行一件任务,每件任务只有在先前的任务完成后才开始。同时,你并不知道一个任务完成到另一个任务开始之间的间隔时间,如下图所示:
任务的执行是在GCD掌控之下的;你唯一确定的就是GCD在同一时刻只执行一件任务并且按任务加入队列的顺序执行。 因为不会在顺序队列中同时执行两件任务,所以没有多个任务同时进入临界区的危险;这保证了临界区不会出现竞态条件。因此如果进入临界区的唯一途径就是通过向调度队列提交任务,那么可以保证临界区是安全的。 并发队列
并发队列中的任务可以保证按进入队列的顺序被执行…仅此而已!任务可能以任意顺序完成而且你不知道何时下一个任务会开始,或是任一时刻有多少任务在运行。再一次,这完全取决于GCD。 下图展示了四个并发任务的例子:
任务1,2和3都运行的很快,一个接一个。但是任务1在任务0开始了一段时间后才开始。同时,任务3在任务2开始后才开始但是却更早完成。 何时开始一个任务完全取决于GCD。如果一个任务的执行时间和另一个的发生重叠,将由GCD来决定是否要将任务运行在另一个可用的核上或是通过上下文切换来运行另一个程序。 有趣的是,GCD为每种队列类型提供了至少5种特别的队列。 队列类型 首先,系统提供了一种特殊的顺序队列main queue。和其他的顺序队列一样,在这个队列里的任务同一时刻只有一个在执行。然而,这个队列保证了所有任务会在主线程中执行,主线程是唯一一个允许更新UI的线程。这个队列用来向UIView对象发消息或发通知。 系统同时提供了几种并发队列。这些队列和它们自身的QoS等级相关。QoS等级表示了提交任务的意图,使得GCD可以决定如何制定优先级。
要清楚Apple的API同时也使用了全局调度队列(global dispatch queue),所以你添加的任何任务都不是这些队列中的唯一任务。 最后,你可以创建自定义的顺序或并发队列。意味着你至少有5种队列:主队列(main queue),四种通用调度队列,加上任意你自己定制的队列! 以上就是调度队列的主要部分! GCD的“艺术”可归结为选择正确的队列调度函数来提交任务。最佳的学习方式就是通过下面的例子。 示例 因为这篇教程的目标是使用GCD优化程序以及在不同线程中安全的运行代码,所以你会以一个几近完成的项目GooglyPuff来开始。 GooglyPuff是一个未优化,非线程安全的app,使用Core Image的人脸识别API在人脸上叠加金鱼眼。初始图像可以从图片库中选择或是从网络下载一组预定的图片。
一旦下载了工程,提取到合适的地方,打开Xcode并运行它。看起来如下:
注意:当你选择Le Internet选项来下载图片时,一个UIAlertController提示框会过早的弹出。你会在教程的第二部分修复这个问题。 这个工程中有4个需要关心的类: – PhotoCollectionViewController:app启动后的第一个视图控制器。展示所有选择的图片的缩略图。 – PhotoDetailViewController:为图片加上金鱼眼并在UIScrollView中展示。 – Photo:描述图片属性的协议。提供图片,缩略图和状态。两个类实现了这个协议:DownloadPhoto从NSURL实例化图片,AssetPhoto从ALAsset实例化图片。 – PhotoManager:管理所有Photo对象。 使用dispatch_sync处理后台任务 返回app并从图片库中添加一些图片或使用Le Internet选项下载一些。 留意在轻触PhotoCollectionViewController中的UICollectionViewCell后要多久才能完成PhotoDetailViewController的初始化;此时存在明显的延迟,尤其是在较慢的设备上浏览较大的图片时。 一不小心就会在UIViewController的viewDidLoad中填充过多杂乱的方法而造成超负荷;以至于经常要等待很久视图控制器才会出现。如果可能的话,最好将一些工作转移到后台去完成,如果这些工作在加载时不是必需的。 听起来是使用dispatch_async的时候! 打开PhotoDetailViewController然后用下面的实现替换viewDidload:
|
请发表评论