Swift 3 中的 GCD 与 Dispatch Queue

swiftgg翻译组 浏览次数: 2016-11-30 23:13

译者:小锅;校对:saitjr;定稿:CMB 自中央处理器(CPU)出现以来,最大的技术进步当属多核处理器,这意味着它可以同时运行多条线程,并且可以在任何时刻'...

译者:小锅;校对:saitjr;定稿:CMB

自中央处理器(CPU)出现以来,最大的技术进步当属多核处理器,这意味着它可以同时运行多条线程,并且可以在任何时刻处理至少一个任务。

串行执行以及伪多线程都已经成为了历史,如果你经历过老式电脑的时代,又或者你接触过搭载着旧操作系统的旧电脑,你就能轻易明白我的话。但是,不管 CPU 拥有多少个核心,不管它有多么强大,开发者如果不好好利用这些优势 ,那就没有任何意义。这时就需要使用到多线程以及多任务编程了。开发者不仅可以,而且必须要好好利用设备上 CPU 的多线程能力,这就需要开发者将程序分解为多个部分,并让它们在多个线程中并发执行。

并发编程有很多好处,但是最明显的优势包括用更少的时间完成所需的任务,防止界面卡顿,展现更佳的用户体验,等等。想像一下,如果应用需要在主线程下载一堆图片,那种体验有多糟糕,界面会一直卡顿直到所有的下载任务完成;用户是绝对不接受这种应用的。

在 iOS 当中,苹果提供了两种方式进行多任务编程:Grand Central Dispatch (GCD)NSOperationQueue。当我们需要把任务分配到不同的线程中,或者是非主队列的其它队列中时,这两种方法都可以很好地满足需求。选择哪一种方法是很主观的行为,但是本教程只关注前一种,即 GCD。不管使用哪一种方法,有一条规则必须要牢记:任何操作都不能堵塞主线程,必须使其用于界面响应以及用户交互。所有的耗时操作或者对 CPU 需求大的任务都要在并发或者后台队列中执行。对于新手来说,理解和实践可能都会比较难,这也正是这篇文章的意义所在。

GCD 是在 iOS 4 中推出的,它为并发、性能以及并行任务提供了很大的灵活性和选择性。但是在 Swift 3 之前,它有一个很大的劣势:由于它的编程风格很接近底层的 C,与 Swift 的编程风格差别很大, API 很难记,即使是在 Objective-C 当中使用也很不方便。这就是很多开发都避免使用 GCD 而选择 NSOperationQueue 的主要原因。简单地百度一下,你就能了解 GCD 曾经的语法是怎么样的。

Swift 3 中,这些都有了很大的变化。Swift 3 采用了全新的 Swift 语法风格改写了 GCD,这让开发都可以很轻松地上手。而这些变化让我有了动力来写这篇文章,这里主要介绍了 Swift 3 当中 GCD 最基础也最重要的知识。如果你曾经使用过旧语法风格的 GCD(即使只用过一点),那么这里介绍的新风格对你来说就是小菜一碟;如果你之前没有使用过 GCD,那你就即将开启一段编程的新篇章。

在正式开始讨论今天的主题前,我们需要先了解一些更具体的概念。首先,GCD 中的核心词是 dispatch queue。一个队列实际上就是一系列的代码块,这些代码可以在主线程或后台线程中以同步或者异步的方式执行。一旦队列创建完成,操作系统就接管了这个队列,并将其分配到任意一个核心中进行处理。不管有多少个队列,它们都能被系统正确地管理,这些都不需要开发者进行手动管理。队列遵循 FIFO 模式(先进先出),这意味着先进队列的任务会先被执行(想像在柜台前排队的队伍,排在第一个的会首先被服务,排在最后的就会最后被服务)。我们会在后面的第一个例子中更清楚地理解这个概念。

接下来,另一个重要的概念就是 WorkItem(任务项)。一个任务项就是一个代码块,它可以随同队列的创建一起被创建,也可以被封装起来,然后在之后的代码中进行复用。正如你所想,任务项的代码就是 dispatch queue 将会执行的代码。队列中的任务项也是遵循 FIFO 模式。这些执行可以是同步的,也可以是异步的。对于同步的情况下,应用会一直堵塞当前线程,直到这段代码执行完成。而当异步执行的时候,应用先执行任务项,不等待执行结束,立即返回。我们会在后面的实例里看到它们的区别。

了解完这两个概念(队列和任务项)之后,我们需要知道一个队列可以是串行或并行的。在串行队列中,一个任务项只有在前一个任务项完成后才能执行(除非它是第一个任务项),而在并行队列中,所有的任务项都可以并行执行。

在为主队列添加任务时,无论何时都要加倍小心。这个队列要随时用于界面响应以及用户交互。并且记住一点,所有与用户界面相关的更新都必须在主线程执行。如果你尝试在后台线程更新 UI,系统并不保证这个更新何时会发生,大多数情况下,这会都用户带来不好的体验。但是,所有发生在界面更新前的任务都可以在后台线程执行。举例来说,我们可以在从队列,或者后台队列中下载图片数据,然后在主线程中更新对应的 image view。

我们不一定需要每次都创建自己的队列。系统维护的全局队列可以用来执行任何我们想执行的任务。至于队列在哪一个线程运行,iOS 维护了一个线程池,即一系列除主线程之外的线程,系统会从中挑选一至多条线程来使用(取决于你所创建的队列的数据,以及队列创建的方式)。哪一条线程会被使用,对于开发者来说是未知的,而是由系统根据当前的并发任务,处理器的负载等情况来进行“决定”。讲真,除了系统,谁又想去处理上述的这些工作呢。

我们的测试环境

在本文中,接下来我们会使用几个小的,具体的示例来介绍 GCD 的概念。正常情况下,我们使用 Playground 来演示就可以了,并不需要创建一个 demo 应用,但是我们没办法使用 Playground 来演示 GCD 的示例。因为在 Playground 当中无法使用不同的线程来调用函数,尽管我们的一些示例是可以在上面运行的,但并不是全部。因此,我们使用一个正常的工程来进行演示,以克服所有可能碰到的潜在问题,你可以在这里下载项目并打开。

这个工程几乎是空的,除了下述额外的两点:

    在 ViewController.swift 文件中,我们可以看到一系列未实现的方法。每一个方法中,我们都将演示一个 GCD 的特性,你要做的事情就是在在 viewDidAppear(_:) 中去除相应方法调用的注释,让对应的方法被调用 。

    在 Main.storyboard 中,ViewController 控制器添加了一个 imageView,并且它的 IBOutlet 属性已经被正确地连接到 ViewController 类当中。稍后我们将会使用这个 imageView 来演示一个真实的案例。

现在让我们开始吧。

认识 Dispatch Queue

在 Swift 3 当中,创建一个 dispatch queue 的最简单方式如下:

let queue = DispatchQueue(label: "com.appcoda.myqueue")

你唯一要做的事就是为你的队列提供一个独一无二的标签(label)。使用一个反向的 DNS 符号(”com.appcoda.myqueue”)就很好,因为用它很容易创造一个独一无二的标签,甚至连苹果公司都是这样建议的。尽管如此,这并不是强制性的,你可以使用你喜欢的任何字符串,只要这个字符串是唯一的。除此之外,上面的构造方法并不是创建队列的唯一方式。在初始化队列的时候可以提供更多的参数,我们会在后面的篇幅中谈论到它。

一旦队列被创建后,我们就可以使用它来执行代码了,可以使用 sync 方法来进行同步执行,或者使用 async 方法来进行异步执行。因为我们刚开始,所以先使用代码块(一个闭包)来作为被执行的代码。在后面的篇幅中,我们会初始化并使用 dispatch 任务项(DispatchWorkItem)来取代代码块(需要注意的是,对于队列来说代码块也算是一个任务项)。我们先从同步执行开始,下面要做的就是打印出数字 0~9 :

 1 2 3 4 下一页 尾页
网友点评
猜你喜欢