• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

【翻译】Swift 3中的 Grand Central Dispatch(GCD)和Dispatch Queues

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

本文章翻译自appcoda,作者:Gabriel Theodoropoulos

 多核多线程技术是中央处理单元(CPU)自出现以来最大的技术改进之一,此技术的存在意味着CPU可以在任何时刻同时执行多个任务。

 串行任务执行或伪多任务执行已经存在多年,如果你经历过使用老式电脑的时代,或者如果你曾经有机会用旧操作系统的老式电脑,那么很容易理解我在说什么。但是无论CPU的内核数量多少,功能的强大程度如何,如果开发人员不利用这些可能性,这将变得毫无用处,因为才能让多任务和多线程编程发挥作用。开发人员可以且应该在任何设备上充分利用CPU的多任务功能,将程序可拆分的各个部分拆分到多个线程上并发执行。

 多核多线程开发的优点有很多,最重要的有:能尽快地执行优先度高的任务、给用户最好的体验、避免界面卡顿等等。试想看,假如某些应用程序在主线程上下载一大堆图像,下载完成前UI将完全无发响应任何操作。可想而知,用户将不会再想去使用这样的应用程序。

 在iOS中,苹果提供了两种方式去实现多任务处理:Grand Central Dispatch(GCD)和NSOperationQueue框架,这两者都能完美地分发任务于不同线程或者不同队列中。要使用哪一者取决于开发人员的决定,而这篇教程中我们将会把重点放在GCD的使用上。记住,多任务执行的使用有一条铁则:主线程应该永远用于界面展示和用户交互,任何耗时的或者高占用CPU的任务应该被放在并发队列或者后台队列中。这铁则对于新入门的开发人员来说或许难以消化且运用起来,但也必须要遵守它。

 Grand Central Dispatch最初是在iOS 4中引入的,它在尝试实现并发性、高性能和并行任务时提供了很大的灵活性和可定制性。在Swift 3之前,它有一个很大的缺点:晦涩的方法名让人难以记住和使用,因为它的编码风格与C相似,完全与Swift或者Objective-C的编码风格不同。这也是很多开发者故意避开GCD的主要原因,从而选择使用NSOperationQueue。网络上随意搜索有关于旧版本Swift中使用GCD的文章,都能让你更新UI感受到过去使用GCD的语法有多糟糕。

 然而在Swift 3中这一情况不再存在。GCD的使用方式是全新的,完全类似于Swift,新的语法使得开发人员更容易熟悉它。这些改变让我有动力写一篇关于Swift 3中GCD的最基本和最重要用法的文章。如果你使用的是旧的编码风格(甚至是一点点)的GCD,那么这个全新的语法将让你无法抗拒;如果不是,那么你也很快能驾轻就熟。

 在我们进入正文之前,我们先来谈一些关于GCD的概念。首先,GCD中的核心是dispatch queue。一个队列(queue)实际上是一个可以在主线程或后台线程上同步或异步执行的代码块。队列一旦被创建,操作系统便会接手对它的管理,并给它在CPU的任意内核上运行的时间。多队列也会有相应的管理,而这是开发人员不必处理的。队列之间是遵循FIFO(先进先出)模式的,这意味着首先被执行的队列代码也将首先完成(想象它像是在柜台前面排队的人,排前头的优先享受服务,排最尾的最后享受服务),我们后面会在例子中说明这一点。

 另一个重要的概念是work item。一个work item其实就是将在dispatch queue中运行的代码块,可以在创建队列的同时写入队列,也可单独创建后让队列调用且它可被多次使用(重用)。队列中work item的执行顺序也遵循FIFO模式。Work item的执行可以是同步的或异步的。在同步的情况下,正在运行的应用程序在完成work item的代码前不会退出该代码块。而异步情况下,正在运行的应用程序会在调用work item后立即返回。同样,我们将在后面例子中看到这些差异。

 上面所提到了(dispatch queue和work item),在此说明队列的串行(serial)和并发(concurrent)之分。在串行情况下,一个work item将在上一个work item执行完后开始执行(除非它是在队列中分配的第一个work item),而在并发情况下,不同work item将并行(*译者注:这里不应该是并行,而应该是并发具体可见并发与并行的区别)同时执行。

 当我们想要将任务分配到应用程序的主队列(main queue)时,必须保持谨慎。因为主线程应该始终保持可服务于用户的交互和用户界面的展示。说到这里,还有另一个铁则就是,当你想要作UI上的任何更新时必须始终在主线程上完成。如果你尝试在后台线程上进行UI更新,则无法保证是否会更新以及何时进行更新,并且很可能发生会让用户感受到不愉快的意外情况。但是,更新UI前所作的准备任务必须都已经完成,而这些任务完全允许后台进行。例如,你在次要的后台队列中下载图像的数据,但是你应该回到主线程上更新这些图像。

 需要记住的是,队列并不总是需要自己创建的。系统会创建全局调度队列(global dispatch queues),可被用于任何想要你想要运行的任务。关于队列所在的线程,iOS维护Apple称之为线程池(a pool of threads)中的线程,意思是除主线程以外的线程集合,并且系统会选择一个或多个线程来使用(取决于创建队列的多少以及创建它们的方式)。使用哪个线程是开发人员无法控制的,而是由操作系统根据其他并发任务的数量,处理器的负载等情况来“决定”,我相信也不会有谁想要去自己控制。

测试环境

 在这篇文章中,我们将使用小巧且具体的例子来阐述GCD的概念。通常情况下,我们不会去制作应用程序demo,而只用Xcode Playground进行工作,但是Playground中并不支持GCD的多线程使用所以,我们将通过建立一个工程项目来克服所有有可能出现的困难,你可以在这里下载到它

这几乎是一个空的工程,只做了以下两个添加:

  1. 在这个ViewController.swift文件中,你会发现一个定义但未实现的方法列表。在列表的每一个方法中,我们都会遇到GCD的一个新特性,而你需要做的就是在viewDidLoad(_:)方法取消注释其中一个从而调用它们。
  2. Main.storyboardViewController视图中你会找到已经添加好的imageView,并已通过IBOutletViewController类中属性产生关联稍后我们将通过这个imageView来演示实际开发中的案例。

下面将正式进入正文。

Dispatch Queues入门

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

 创建队列时你只需要给它赋予一个唯一的标签。使用逆序的DNS符号(“com.appcoda.myqueue”)是非常好的选择,因为它十分独特,这也是苹果推荐的做法。但这不是强制性的,只要保证标签唯一,使用任何字符串都是允许的。然而,队列的初始化方式不仅仅只有这一种;其实你可以在初始化时赋予更多的参数,我们会在后面谈论它。

 一旦队列被创建,我们就可以用它来执行代码,可以使用一个叫做sync的方法来同步执行,或者使用一个叫做async的方法来异步执行。因为我们刚开始入门,所以使用代码块(闭包)的方式来执行代码。稍后我们将初始化并使用派发任务项(DispatchWorkItem)对象而不是代码块(请注意:代码块在队列中也被视为任务项)。我们将从同步执行开始演示,该演示仅简单地打印数字0到9。

红点是为了轻易区分控制台中的打印结果,特别是当我们添加更多的队列或执行更多任务的时候。

 上面的for循环将在主队列上执行,而第一个循环将在后台运行。程序执行将在队列中停止; 它不会继续到主线程的循环,直到队列的任务完成,它不会显示从100到109的数字而这是因为我们做了一个同步执行。你也可以在控制台中看到

 将上面的代码段复制粘贴到上面提供的Starter Project下ViewController.swift文件的simpleQueues()方法中。确保此方法未在viewDidAppear(_:)方法中被注释,然后运行项目。注意Xcode控制台,你会发现没有发生什么奇特的事情,只会看到一些数字出现,而这并不能帮助我们得出有关GCD如何工作的结论。因此,你需要在simpleQueues()方法中后执行另外的代码块,它将执行打印100到109的数字(为了与上者区分):


 这个for循环将在主队列上执行,而之前的循环将在后台运行。程序将会停留在queue.sync的代码块中执行任务,不会继续执行主线程中打印100到109数字的代码,除非queue.sync代码块中的任务完成。这是因为我们对queue调用了同步执行的方法,你可以在控制台中看到


 那假如我们使用async方法从而让队列的代码块异步执行,会怎么样呢?其实在这种情况下,程序不会再等待队列任务的完成才执行后面的代码,而是会立即返回到主线程,第二个for循环将与“queue”中的循环同时执行。在我们看到打印前,请你使用以下async方法更新“queue”调用的方法:


 然后,运行并留意Xcode的控制台:



 同步执行相比,这种打印情况更为有趣; 
你会看到主队列(第二个
for循环)上的代码和我们的“queue”的代码并行运行。我们的自定义队列在开始的时候可以获得更多的运行时间,但这只是一个优先级问题(接下来我们会看到它)。在这里我们更重要的是要弄清楚:当我们有另一个任务在后台运行,而且该队列的任务项不是在主线程以同步执行时,我们的主队列是自由“工作”的

 尽管上面的例子非常简单,但它清楚地体现了程序在同步队列和异步队列的运行行为不同。我们将在后面继续使用彩色的打印方式,要记住某个颜色是指在某个队列的任务执行结果,所以不同的颜色意味着不同的队列。

Quality Of Service (QoS) 和 Priorities

 在使用GCD和dispatch queue时,常常需要告诉系统:你的应用程序中哪些任务相对于其他任务更需要优先执行。当然,在主线程上运行的任务总是具有最高的优先级,因为主队列负责处理UI并时刻保持程序能响应操作。无论如何,通过向系统提供这些信息,iOS会保证列队以优先级顺序排列并提供所需的资源(如CPU的执行时间)。很自然,所有的任务最终都会得到完成,而区别在于哪些任务会更快完成,哪些更晚。

 任务的重要程度和优先级相关的信息称为GCD 服务质量(Quality of Service,简写为QoS)事实上,QoS是一组具备特定类型的enum,通过在队列初始化时提供适当的QoS值,可以指定所需的优先级。如果没有定义QoS,则队列会给定优先级的默认值。QoS可选值的文档可以在这里找到,请保证你会阅读该网页。以下列表总结了可用的QoS情况,也称为QoS类型。排第一意味着最高优先级,排最后是最低优先级:

  • userInteractive
  • userInitiated
  • default
  • utility
  • background
  • unspecified

 现在回到我们的项目中,我们将在queuesWithQoS()方法里进行操作。声明并初始化以下两个新的调度队列:


 可以注意到,我们给它们分配了相同的QoS类型,所以它们在执行期间具有相同的优先级。就像我们之前所做的那样,第一个队列将包含一个for循环以显示0到9的值(加上红点)在第二个队列中,我们将执行另一个for循环,它将显示从100到109的值(加上蓝点)。


 我们现在看看,执行明知两个具有相同的优先级(相同QoS类型)队列的结果——不要忘了取消queuesWithQos()方法中queuesWithQos()的注释:


 通过查看上面的截图可以很容易地看出,这两个任务都是“均匀”执行的,实际上这是我们期望得到的结果。现在,让我们改变queue2的QoS类型为 utility(低优先级),如下所示:


 让我们看看会发生什么:


 毫无疑问,第一个调度队列(queue1)比第二个调度队列执行得更快,因为它的优先级更高。即使queue2在一开始获得执行的机会,系统还是将其资源主要提供给第一个队列,因为它被标记为更重要的一个。一旦第一个队列完成,系统才会关注第二个队列。

 我们来做另一个实验,这次我们将第一个队列的QoS类型改为background(后台)



 这个优先级几乎是最低的,我们来看看在运行代码时会发生什么:


 这次第二个队列的完成执行更快,因为QoS类型中background比utility具有更高的优先级。

 以上内容让我们明确了QoS如何工作,但假如我们在主队列上执行任务会如何?让我们在方法的末尾添加下面的代码:


 结果如下:


 我们可以再次看到主队列默认具有高优先级,并且queue1队列与主队列并行执行。而queue2队列在其他两个队列的任务完成执行前没有得到很多的执行机会,是因为它的优先级最低。

Concurrent Queues(并发队列)

 到目前为止,我们已经看到dispatch queue是如何同步和异步地工作,以及Quality of Service各类型如何影响系统赋予队列的优先级。前面所有例子的共同点是,我们这些队列是串行(serial的。这意味着,如果我们将多个任务分配给其中某个队列,那么这些任务将一个接一个地按顺序执行,而不是同时一起执行。接下来,我们将看到如何让多个任务(工作项)同时运行,换句话说,我们将看到如何创建一个并发队列。

 进入我们的项目,来到concurrentQueues()方法(我们将取消viewDidAppear(_:)相应方法注释在这个新的方法中,让我们创建以下的新队列:


 现在,让我们将以下任务(或者称为工作项)分配给队列:


 当这段代码运行时,任务将以串行模式执行。这可在截图中很清楚看到:


 接下来,让我们修改anotherQueue队列的初始化


 上述初始化有一个新的参数:attributes参数。该参数被赋予concurrent值时,队列的所有任务将被同时执行。如果你不指定这个参数,那么得到的会是一个串行(serial)队列。另外,QoS参数不是必需指定的的,我们可在这个初始化中省略它,也是不会有任何问题的。

 通过再次运行程序,我们注意到这些任务几乎是并行执行的:


 请注意,修改队列的QoS类型对任务的执行同样会有影响。但是,只要你将队列初始化为并发队列,那么这些任务的并行(译者注:此处应该是并发)执行就会受到尊重,并且都将得到运行时间。

 该attributes


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
[Swift]LeetCode650. 只有两个键的键盘 | 2 Keys Keyboard发布时间:2022-07-13
下一篇:
swift 集合类型(一)发布时间:2022-07-13
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap