入门Swift编程第11部分-大型中央调度和关闭

以前,我们了解了代码结构,可读性和其他一些原理。

Swift编程入门第10部分-代码结构,可读性和原理

在上一篇文章中,我们介绍了基本协议,扩展和下标。

medium.com

是的,它不是技术性很强的工具,但是如果您一直在磨练自己的技能,那么您的项目就会变得相当庞大,并且您可能已经开始考虑可以使代码井井有条的方式。 项目之间的代码结构各不相同,因为在开始首次工作编程之前,开发人员很少谈论如何布局代码。 即使那样,它大部分时间还是安静的,只是希望您能赶上。 如果您尝试自己编写应用程序,则这样做的效果不太好。

当我们讨论了可读性时,我几乎不讨论任何事情,但是我为您提供了基础知识,因此当您回到代码中时,就不会迷失方向。 我可以只写一篇关于代码结构或可读性的整篇文章,但是为了使事情快速发展,我略过了。 如果您想了解有关结构或可读性的更多信息,请告诉我,本系列结束后,我将计划一些深入的文章。

最后,我们介绍了一些原则以为您提供指导,以及更多有关我为何以这种方式编写代码的上下文。

让我们开始吧。

Grand Central Dispatch是Apple处理称为调度队列的方法。 队列有三种类型:

  • 串行 -按接收顺序执行发送到队列的工作,先入先出(FIFO)。 这些也称为专用调度队列。
  • 并发同时执行发送到队列的工作。 每个任务的启动顺序与将它们添加到队列的顺序相同。 串行队列和并发队列之间的主要区别在于,在串行队列中,下一个任务要等到第一个任务结束后才开始。在并发队列中,下一个任务可能不需要与第一个任务一样多的时间来完成,并且可能在第一个任务之前完成第一个任务完成。 在决定使用哪个队列时,请记住这一点。 这些也称为全局调度队列。
  • 主调度队列 -这是应用程序的主线程,或应用程序所在的位置。 当您将代码放入View Controller的viewDidLoad()时,这就是所有工作的队列。

我将暂时脱离所有这些,以使您了解这些队列与硬件的关系。

在这里,我提供了排队时任务的快照。 在继续之前,我想先介绍几个定义。

  • 运行循环-您的程序只有在告诉您时才停止,而在运行时,它会在while循环下运行,直到结束。 有时它需要处理事物,而其他时候,它只是简单地通过循环而没有任何变化。
  • 线程-简单地说,这些是您的单独任务,如果您的任务需要另一个任务来完成其自己的过程,它将在另一个线程中处理该任务。 (不要与CPU Core混淆)
  • CPU —计算机的大脑,这是使用一个或多个内核执行计算的设备。
  • 核心-核心是处理器的物理或逻辑部分,可读取您的代码并返回结果。 每个核心都有许多硬件线程,可用于执行在其上发送的任务。
  • CPU Clock Speed(CPU时钟速度)—处理器读取1s和0s(二进制)的速度。 如果您曾经想过1 GHz意味着什么,Giga意味着十亿,那么1 GHz意味着处理器的每个内核每秒可以读取10亿个1或0。 我的开发计算机的处理器速度为2.8 GHz,具有8个内核。 这意味着它可以在其基本时钟速度下每秒处理2.8 * 8 = 224亿个零。 如果您想知道时钟是否与运行循环有关,答案是肯定的。 时钟执行滴答声,每个滴答声是1或0。
  • 指令周期-有时称为周期,用于讨论CPU周期,这是CPU读取的一组指令,通过一系列的滴答声来执行任务。

根据上面的示例,Core 0负责运行您的应用程序。 主线程是您的应用程序。 如您所见,我添加了run循环以表明它永远不会结束,直到您通过退出应用程序使其停止为止。

当您的应用程序运行时,它可能会创建需要异步执行的任务,或者在您的应用程序执行其他操作的同时执行。 为此,我们在另一个内核中创建一个新线程。

如果我们将其发送到全局队列(并发),则将其放置在下一个可用核心上,并在收到后立即进行处理。

如果我们将其发送到专用队列(串行),则将其放置在下一个可用核心上,并在核心可以将全部精力集中于任务上时立即进行处理。 这通常在几毫秒内发生。

在只有一个处理器的旧系统上,这些任务将在同一内核上同时运行,问题是每个任务都会被系统中断,因此系统可以在这两个任务之间执行工作。

在较新的系统上,此问题仍然存在,但由于最多有8个内核能够执行工作,因此问题并不那么明显。

回到上图,任务1和任务2在两个单独的内核上同时工作,但是由于我们只有3个内核,因此任何后续任务都必须等待当前任务结束。

如果要在队列中添加另一个并发任务,则最终可能会导致两个任务之间的核心之一关闭,这将大大降低两个任务的处理速度。 请注意您正在运行多少个并发任务。

任务1和3将被视为按顺序运行,任务3将在任务1完成之前开始。 任务4可能已由任务3创建,以同时执行任务3所需完成的工作,例如从服务器下载数据。

如果任务3尚未完成,则可以串行创建任务5,或者如果任务3已结束,则可以使用任一队列创建任务5。 最常见的是,当您在Xcode Instruments中看到此模式时,它将顺序创建。

可以使用任一队列创建任务6,因为它是独立的并且所有内核都可用。

在更深层次的层次上(这里不会介绍),处理器可以执行一些优化以改善任务的调度,即使它们是串行的还是并发的,所以我对这些任务的创建方式的解释可能与调试时看到的有所不同。应用的性能。 这只是调度工作原理的更高层次的概述。

因此,我们讨论了计划任务时的情况,但是我们没有讨论如何在不同的队列中计划任务。 首先,让我们讨论不同的队列优先级(QoS),然后为您展示代码。

  1. 用户交互 (主线程)-应用程序的主队列,这是在应用程序中编写逻辑时的默认值,它不是后台线程。 当用户点击按钮时,所有用户界面(UI)项都在此队列上使用,它在主线程上执行。 我们希望放置在主队列中的工作立即发生
  2. 用户发起的 —用户的交互是最重要的,但是有时用户发出的请求可能需要一段时间。 如果您在主线程上执行此工作,它将冻结UI并导致用户卡住。 相反,这很不好,因为我们使用了用户启动的任务优先级,因为用户需要该任务尽快返回其结果。 这将在游戏中更新时使用,或者在用户从服务器检索位置列表以加载到地图视图中时使用。 工作几乎立即发生
  3. 默认值 -这是分配了后台任务的默认队列。 优先级要比用户启动的优先级低,因为用户现在或不久的将来都不需要它来使用您的应用。 不提供队列优先级时使用。 工作可能需要几分钟
  4. Utility(实用程序) —当您了解编程领域中的实用程序时 ,这是自描述的。 这是用于发出网络请求以下载非关键数据或将数据持久存储到磁盘。 工作需要几秒钟到几分钟
  5. 背景 -这是用于在您的应用中执行维护的任务,对您来说很重要,但用户并不了解索引。 工作可能需要几分钟

按此顺序,任何具有较高优先级的任务(最高的1)都可以中断具有较低优先级的任务。 确保您进行相应的编码。 每个任务还​​具有相对于其自己的队列的相对优先级,范围在0到-15之间,因此您可以在每个后台队列中获得一些调度优先级。

如果您来自另一种语言,那么您已经知道什么是闭包以及如何使用它们。 它们在其他语言中称为块或lambda。 闭包主要用于函数式编程,但也有其他用例。

描述闭包的最简单方法是说闭包是可以在代码中作为变量传递并在代码中稍后执行的逻辑

关于闭包,您应该了解的第一件事是它们是引用类型。

您应该知道的第二件事是,它们大多数时候都是异步的。 这意味着您应该格外小心,以确保在使用该闭包时没有其他更改,并且您需要确保所有长时间运行的闭包在尝试获取它们的价值之前都已完成。

您应该了解的最后一件事是,闭包通过捕获闭包中使用的所有内容来工作。 这意味着,如果我在文件的顶层设置了变量,并在闭包中使用它,闭包将创建对该变量的强引用。 您需要注意不要拥有已经拥有您的闭包的对象。 如果ClassA创建了闭包,则闭包不应强烈引用ClassA 。 您可以通过使用weakunowned关键字来使用它,我将向您展示如何做到这一点。

首先,让我们以函数的返回类型来看其基本形式的闭包: