Tag: 并发

核心数据第2部分。 —子上下文和多个托管对象上下文

多个托管对象上下文 Core Data的默认配置为您提供了与主队列关联的单个托管对象。 刷新托管对象上下文是一个内存暂存器,您可以在使用托管对象时使用它。 什么时候需要多个托管对象上下文? 1.编辑数据→可以将托管对象上下文视为一组更改,应用程序可以通过子上下文丢弃这些更改。 可以将编辑的数据视为新信息,例如便笺本。 数据编辑后,您可以保存或删除。 2.导出数据(长时间运行的任务)→仅使用单个主队列管理对象上下文来阻止应用程序的主线程,然后UI会被阻止并且无法正确更新。 导出数据时的默认行为是让您的导出操作和UI都使用主队列执行其工作,但是如果此导出是一项长期运行的任务,则UI将滞后并且会有延迟。 传统上,您可以将数据导出运行到后台队列中,但是Core Data受管对象上下文不是线程安全的。 您不能将操作分派到后台队列,也不能使用相同的Core Data Stack。 Apple开发者网站上有关您的上下文的一些上下文: 通常,避免在与用户无关的主队列上进行数据处理。 数据处理可能会占用大量CPU,并且如果在主队列上执行数据处理,则会导致用户界面无响应。 如果您的应用程序将要处理数据,例如将数据从JSON导入到Core Data中,请创建一个私有队列上下文并在该私有上下文上执行导入。 以下示例显示了如何执行此操作: 让我们看一下第5行中发生的情况。新上下文设置为运行该应用程序的主要上下文的子级。 子托管对象上下文是临时暂存器,从本质上讲,它是可编辑更改的容器,可以将其丢弃或修改并随父上下文的更改一起保存。 保存子上下文后,更改仅转到父上下文。 保存父上下文后,父上下文中的更改将发送到持久性存储协调器。 此外,每个管理对象上下文都有一个父存储,如果它是CoreDataStack类提供的主要上下文,则通常是持久存储容器。

核心数据和并发性:

记住这一点: “切勿在线程之间共享托管对象上下文。 这是一条硬规则,你不应该打破。” “ 并发性是同时处理多个队列上的数据的能力。 ” 如果不了解多线程应用程序中的场景和用例,则对核心数据概念的了解将是不完整的。 老实说,Apple文档没有以简单的方式解释这些用例。 让我们从基础开始: 考虑以下两个问题: 如果从不同的线程访问相同的管理对象上下文,会发生什么情况? 如果将托管对象从后台线程传递到主线程会怎样? Apple文档非常清楚Core数据希望在单线程上运行 ,而不必是主线程。 核心数据并非旨在从不同的线程访问。 但是,苹果公司的核心数据团队并不幼稚。 它知道需要从多个线程访问持久性框架。 单个线程(主线程)可能适合许多应用程序。 更复杂的应用程序需要一个健壮的多线程持久性框架。 在多线程应用程序中访问核心数据的基本规则: 托管对象: NSManagedObject 实例绝不能从一个线程传递到另一个线程。 如果需要将托管对象从一个线程传递到另一个线程,请使用托管对象的 objectID 属性。 objectID 属性的类型为 NSManagedObjectID 并且唯一标识持久性存储中的记录。 当您将其交给NSManagedObjectID实例时,托管对象上下文知道该怎么做。 您需要了解三种方法: object(with:) existingObject(with:) registeredObject(for:) 第一个方法 object(with:) 返回与 NSManagedObjectID 实例 相对应的托管对象 。 如果托管对象上下文中没有该对象标识符的托管对象,它将询问持久性存储协调器。 此方法始终返回一个托管对象。 知道 如果找不到接收到的对象标识符的记录 ,则 object(with:) 会引发异常。 例如,如果应用程序删除了与对象标识符相对应的记录,则Core Data无法将您的应用程序交给相应的记录。 结果是一个例外。 existingObject(with:) 方法的行为类似。 主要区别在于,如果该方法无法获取与对象标识符相对应的托管对象,则该方法将引发错误。 […]

并发队列中的原子属性

关于原子如何工作的研究~~~ 从我关于引用计数的文章中,我对原子的工作原理以及它不是线程安全的方法很感兴趣。 因此,本文致力于并发操作中有关原子属性的一些示例代码。 并发队列可以包含多个任务,但是任务的顺序由操作系统管理,这将在后面显示。 根据Apple官方网站,我们可以创建三种主要的Dispatch Queue类型: 并发队列可以很容易地创建如下: 现在,我将创建一个将在队列上运行的函数: 为了在队列上运行testRunner ,您需要通过dispatch_async调用它们: 由于testRunner只循环一次,因此OS可以轻松地连续照顾它们,显示NS​​Logs: 但是,如果将循环增加到5,则它们将不会连续运行: 为了证明它们是不同的线程,只需在调用该函数的行上放置一个断点,然后您可以检查正在运行的线程: 由于操作是由CPU管理的,因此可能有机会在同一线程上进行操作。 但是,当线程大得多(i <500)时,看起来它们有更大的机会在不同的线程上进行操作。 [我仍然需要阅读更多有关此内容的信息,但是如果您有任何想补充的内容,请给我一个共同的名字,一千个感谢^^] 原子属性怎么样? 在这里,我创建了一个名为AtomicModel的类,该类包含两个属性: atomicString和nonAtomicString 在功能runAtomicThreads中 ,创建并发调度,并创建两个线程,一个线程调用[self atomicSetter],另一个调用[self atomicGetter]。 基本上, atomicSetter的计数从0到100000,并将atomicString设置为偶数为“很长的字符串”,而奇数为“ string”。 尽管atomicGetter可以做同样的事情,但是当atomicString.length≥10时,它将创建一个subStr 。 如果您运行该代码,则当for循环重复一个较小的数字时,您将看到没有错误。 但是,当重复计数增加时,您将有机会反弹到错误: 那么到底发生了什么呢? 为了帮助我理解发生了什么,我在线程B中获取subStr之前和之后都记录了atomicString 。 从线程B打印出的最后一句话看,线程B似乎认为atomicString ==“一个很长的字符串” 让我们看看线程A对此有何看法。 显然,线程A和线程B在atomicString应该是什么值上存在分歧。 因此,当线程B认为atomicString .length≥10时 ,线程A将其更改为长度小于10的“字符串”。 这导致代码崩溃。 结果,这证明原子属性不是线程安全的。 当使用非原子属性时,也会发生相同的错误,唯一的区别是非原子比原子快得多。

快速浏览Grand Central Dispatch和Swift 3

多线程和并发对于现代应用程序是必不可少的……但是,Grand Central Dispatch是用于管理并发操作的系统级库,它具有iOS SDK中较为麻烦且不友好的API之一。 不再。 Swift 3带来了对Grand Central Dispatch语法和用法的许多改进。 这是一些新功能的快速浏览。 dispatch_async 以前,我们将选择调度方法(同步与异步),然后选择要向其调度任务的队列。 更新后的GCD会颠倒顺序-我们首先选择队列,然后应用调度方法。 最常见的GCD模式之一是在全局后台队列上执行工作,并在工作完成后立即更新主队列上的UI。 新API的外观如下: 队列属性 您会注意到,队列现在在init上具有属性。 这是一个Swift OptionSet,可以包括队列选项,例如串行与并发,内存和活动管理选项以及服务质量(.default,.userInteractive,.userInitiated,.utility和.background)。 服务质量取代了iOS8中不推荐使用的旧优先级属性。 如果您习惯了优先级队列,请按照以下方法将它们映射到QOS案例: * DISPATCH_QUEUE_PRIORITY_HIGH:.userInitiated * DISPATCH_QUEUE_PRIORITY_DEFAULT:.default * DISPATCH_QUEUE_PRIORITY_LOW:.utility * DISPATCH_QUEUE_PRIORITY_BACKGROUND:.background 内存和活动管理选项是今年的Apple OS版本(OSX 10.12,iOS 10.0,tvOS 10.0,watchOS 3.0)的新增功能。 这些功能包括使用.initiallyInactive在非活动状态下启动队列或使用.autoreleaseInherit,.autoreleaseNever和.autoreleaseWorkItem为队列设置自定义自动释放设置的功能。 工作项目 队列不是GCD获得Swift OptionSet的唯一部分。 工作项目也有更新的Swift语法: 现在,工作项可以在初始化时声明质量或服务和/或标志。 这两个都是可选的,并且会影响工作项的执行。 这些标志是一个选项集,其中包括以下选项:barrier,detached,assignCurrentContext,noQoS,InheritanceQoS,forceforceQoS。 一次派遣 dispatch_once对于初始化代码和仅执行一次的其他功能非常有用。 在Swift 3中,不建议使用dispatch_once,而应将其替换为全局或静态变量和常量。 dispatch_time_t dispatch_time_t是将指定时间转换为可以提供给队列的UInt64的函数。 更新的GCD为此引入了更友好的语法(告别NSEC_PER_SEC)。 这是一个使用after之后的示例: .seconds是名为DispatchTimeInterval的新枚举的一部分。 这些案例具有一个代表计数的关联值。 目前支持: * […]

Swift并行编程—第1/4部分

并发,并行和调度队列 每当我们听到并行编程时,就会想到两个令人困惑的术语:并发和并行。 首先让我们看看现实生活中的角色: 并发性:我们的日常工作涉及多任务处理。 很多时候,我们执行多任务处理,但最终会同时完成一个任务,但是理想情况下,这就是上下文切换。 它也有一些限制。 假设我们每天有100项工作,但不能将其增加到1000项。 并行性:如果我们可以得到更多的物理东西,那么可以同时完成两个或更多的工作。 如果我有4臂,那么这篇文章可以在一半的时间内完成。 让我们来看看计算机世界中的两个术语: 并发: 并发意味着应用程序同时(并发)在一项以上的任务上取得进展。 如果计算机只有一个CPU,则该应用程序可能无法完全同时执行一项以上的任务,但是一次要在应用程序内部处理一项以上的任务。 在开始下一项任务之前,它并没有完全完成一项任务。 当我们谈论至少两个或更多任务时,并发本质上是适用的。 当一个应用程序几乎可以同时执行两个任务时,我们称其为并发应用程序。 尽管这里的任务看起来像是同时运行的,但实际上它们可能并非如此。 它们利用了操作系统的CPU时间分片功能,其中每个任务运行其任务的一部分,然后进入等待状态。 当第一个任务处于等待状态时,CPU被分配第二个任务以完成其部分任务。 操作系统根据任务的优先级工作,因此分配CPU和其他计算资源,例如内存; 依次处理所有任务,并给他们完成任务的机会。 对于最终用户,似乎所有任务都是并行运行的。 并发复杂度 假设有5个朋友搬进了房子,每个人都有一张床。 哪种更复杂的方式来构造它? 5人同时组装一张床或 每个人组装自己的床 想一想如何为几个朋友编写有关如何将床组装在一起的说明,而无需彼此扶持或等待工具。 他们需要在正确的时间协调动作,以将床的各个部分组装成成品床。 这些指令真的很复杂,很难编写,也可能很难阅读。 每个人都自己建造床时,说明非常简单,无需等待其他人完成操作或使用工具即可。 罗伯·派克 ( Rob Pike)有一个关于 并发不是并行的演讲。 平行性 并行不需要两个任务存在。 通过为每个任务或子任务分配一个内核,它可以使用CPU的多核基础结构同时运行部分任务或多个任务。 并行性本质上要求具有多个处理单元的硬件。 在单核CPU中,您可能会获得并发性,但不能获得并行性。 并发是独立执行的进程的组成,而并行性是计算的同时执行。 并行性是指应用程序将其任务分解为较小的子任务,这些子任务可以并行处理,例如在多个CPU上同时进行。 并发是一次处理很多事情,更多的是关注结构。 并行性是一次执行很多事情,其重点是执行。 一个应用程序可以是并发的,但不能是并行的,这意味着它可以同时处理一个以上的任务,但是两个任务不能同时执行。 一个应用程序可以是并行的,但不能是并行的,这意味着它可以同时处理多核CPU中一个任务的多个子任务。 应用程序既不能是并行的,也不能是并发的,这意味着它一次顺序地处理所有任务。 一个应用程序可以是并行的,也可以是并发的,这意味着它可以同时在多核CPU中同时处理多个任务。 自从出现以来,CPU技术的最大改进之一就是能够包含多个内核,因此可以运行多个线程,这意味着在任何给定时刻可以执行多个任务。 在iOS中,有两种实现并发的方法:Grand Central Dispatch和OperationQueue。 这两个主题都很广泛,因此我将本文分为四个部分 : […]

iOS 2018系列:破解iOS采访或成为iOS专家(6)

第6章:DispatchGroup和OperationQueue 最近,我不得不做一堆异步任务,我只想在所有任务完成时得到通知。 该博客将介绍两种简单的方法:DispatchGroup和OperationQueue。 在这篇文章中,我们将同时介绍它们。 派遣组 如果您正在开发一个不使用Operations的项目,并且不想重构任何现有代码,则DispatchGroup是一个功能强大的API,可以将这些任务组合为一个任务。 情况1 :计算三个不同线程上三个数组的总和,一旦所有线程完成其执行,我想使用该结果。 在考虑解决方案之前,让我们学习一些与调度组相关的API。 enter () —明确指示一个块已进入该组。 leave () -明确表示该组中的一个块已经完成。 func notify (qos:DispatchQoS =默认值,标志:DispatchWorkItemFlags =默认值,队列:DispatchQueue,执行工作:@escaping @convention(block)()-> Swift.Void)– 当一组先前提交的块对象完成时,将要计划的块调度到具有指定服务质量类和配置的队列中。 解决方案 :这三个API将帮助我处理上述情况,让我们学习如何使用它们。 我在下面写了添加数组元素并返回的函数 此函数使用DispatchQueue.global()。async创建线程,并使用DispatchQueue.concurrentPerform创建循环以添加infoArr的所有元素(类似于for循环),然后返回名为block()的完成处理程序。 下面的函数将创建三个并行计算总和的线程,一旦线程完成,它将调用dispatchgroup的notify函数。 调用notify函数后,我们将拥有所有三个数组的和,并且我们可以将其传递给completeizationBlock()。 操作和操作队列 它是一个抽象类,您需要对其进行子类化才能使用。 如果您只想执行一小段代码或调用一个方法,则可以使用BlockOperation和NSInvocationOperation代替子类化Operation。 情况2:使用操作计算三个不同线程上三个数组的总和,一旦所有线程完成其执行,我都想使用该结果。 解决方案:创建OperationQueue实例以添加操作,创建三个BlockOperation以添加数组的总和,然后在操作之间添加依赖项(因为我们需要获得实际上不依赖的最终总和),一旦我们以任何顺序将这三个块操作添加到操作队列中,这无关紧要,在第三块上注册完成块以获取最终总和。 输出:下图显示-调用块的顺序和最终求和结果 有障碍的并发队列 并发队列中的任务可以任何顺序执行,并且可以同时启动。 与上述串行队列相比,这是非常快的。 但是,由于我们可能在写书的同时阅读,所以我们会遇到读者-作家问题。 如果有一种方法可以确保在读取时不进行写入操作,而在使用并发写入时不进行读取操作,该怎么办? 好吧,有一种方法可以使用带有Barrier的Concurrent Queue 。 如果我们采用上面的并发代码,并为写操作插入一个障碍,那么我们将确保在执行队列中的所有读取之后进行写操作,并且确保在写入时不会发生任何读操作。 使用带有障碍的并发队列可以帮助我们改善和加快代码的速度,同时消除读写器问题,这对于单例来说也很重要。 奖励时间:#import和@class之间的区别 “ #import”将整个有问题的头文件带入当前文件; 还包括该文件#imports的所有文件。 另一方面 , @class (当单独使用带有某些类名的行时)告诉编译器“嘿,您很快就会看到一个新令牌; 这是一堂课,所以要这样对待)。 当您有可能使用“ […]

大中央调度(GCD)第二部分

在第一部分中,我们介绍了串行和并发队列以及示例。在这一部分中,我们将详细介绍有关具有参数更改的并发队列的苹果,并学习如何创建全局队列,非活动队列,时间延迟,死锁。 让我们从我们离开的地方开始。 InactiveQueue: 在attribute参数中有另一个值initialInactive。因此,我们可以首先使非活动队列成为队列,当需要活动时,我们可以使它处于活动状态。 让我们将属性值并发更改为initialInactive,如下所示: let newQueue = DispatchQueue(标签:“ com.concurrent.ekram”,qos:DispatchQoS.background,属性:.initiallyInactive) 并在并发函数的外部块中创建DispatchQueue的类属性: var inactiveQueue:DispatchQueue! 现在在这样的函数中使用newQueue初始化不活动队列: inactiveQueue = newQueue 现在,newQueue处于非活动状态,并且viewDidAppear不知道队列,因此需要在viewDidAppear中手动激活,如下所示: 如果让队列= inactiveQueue { queue.activate() } 现在,代码将以串行方式运行并执行队列。请参见屏幕和控制台: 最初不活动时如何使用并发: 属性参数很简单,请同时使用.initiallyInactive和的数组。 并发比队列最初也将处于非活动状态并发。 let newQueue = DispatchQueue(标签:“ com.concurrent.ekram”,qos:DispatchQoS.background,属性:[。initiallyInactive,.concurrent]) 现在的结果是: 因此,当我们使用active()方法在viewDidApper上同时运行时,newQueue最初也处于非活动状态并且是并发的。 创建全局队列: 我们已经知道如何使用property创建自定义队列。 实际上,我们不需要创建全局队列,因为GCD工程师已经为我们创建了全局队列。因此,我们可以像这样创建.. 让globalQueue = DispatchQueue.global() 我们可以添加到全局队列方法 globalQueue.async { 对于i in 0 .. <10 { 打印(“ Custom Green Love:Custom”,i) } } […]

DispatchQueue –ᗧ••Lee –中

Swift快速备忘单— DispatchQueue 用户负责DispatchQueue,操作系统负责基于DispatchQueue的线程 DispatchQueue是一个队列,它接受先进先出而不是线程的工作项。 用户将workItem(closure)放入DispatchQueue 使用测试 具有不同QOS的全局队列 如预期的那样,即使执行时间晚于背景,也会执行.userInteractive 自定义队列:串行队列 序列定制队列中的项目将顺序执行 自定义队列:并发队列 不完全是最新的,但是 2个自定义串行队列 好的,2个自定义串行队列在文件中并发运行。 2个自定义并发队列 好的 从主线程同步背景 好吧,我们什么时候使用这个 来自backgound的异步main:最常见的用法 所以, 如果我想让某些任务在主服务器中依次运行,请使用主队列 如果我希望某些任务在后台顺序运行,请放入单个自定义串行队列。 如果我希望某些任务在粗略意义上并发运行,请放入全局队列。 执行延迟 在线程1中 不错 从thread1 不错

大中央派遣-如何终端! (快速3)

嗯,多线程,并发等等。 这些都是在Flatiron学校的话题,我们被告知“这确实很重要”,现在我开始寻找工作,阅读工作说明并进行技术面试,现在我意识到Flatiron并不是在撒谎。 因此,在这里,我们只关注一件事: 中央中央调度(GCD) 。 Grand Central Dispatch(以前称为libdispatch的框架)是Apple的框架,用于将并发(管理同时发生的多件事)合并到您的应用程序中。 基本上,GCD会为我们管理队列(为我们完成所有任务调度!),因此我们不必考虑如何合并线程或在哪里“锁定”代码行。 GCD允许我们(开发人员)考虑将代码编写为单个线程或进程,因此,我们只需要考虑在哪里分解我们的部分代码以在另一个线程上运行。 Apple文档指出,“ GCD提供并管理FIFO(先进先出)队列,您的应用程序可以以块对象的形式向其提交任务。 提交给调度队列的工作在系统完全管理的线程池上执行。 无法保证执行任务的线程。” 简而言之,GCD是我们的小帮手,因为它: 因为它在后台运行长时间运行的任务,所以有助于提高我们的应用性能。 通过避免在新线程上设置锁和分离代码的需要,使开发人员更轻松(如果操作不正确,可能会导致并发错误) 可以提高代码性能,例如单例。 因此,让我们退后一步,回顾一些在开始讨论GCD之前需要理解的词汇和概念: 任务=需要完成的一项工作(即API调用)。 线程=操作系统提供的进程,它允许多个指令集(又称代码行)在单个应用程序中同时运行。 进程=代码的可执行部分,可能在多个线程中。 太酷了,既然我们已经把这些术语简化了,让我们来谈谈任务的执行方式。 串行调度队列与并发调度队列 如果一项任务一次执行一次,则称它是串行执行的。 如果可以同时执行多个任务,则称它们是同时执行的。 Grand Central Dispatch提供了调度队列(以“队列”为一行),使您可以在所谓的并发调度队列上并发运行代码块。 使用并发调度队列,我们​​有多个线程可用于同时处理多个代码块。 这种队列不同于串行调度队列 , 串行调度队列一次只在一个线程上添加一件工作。 同步与异步执行 函数可以是同步的也可以是异步的。 同步功能仅在上一个任务完成后返回 。 我喜欢将其视为“同步”(有趣的事实:NSYNC是我一直以来最喜欢的男孩乐队)。 异步意味着它可以立即运行,而无需等待上一个任务完成就可以开始。 因此,它不是同步的(我喜欢将异步看作是我的NSYNC的后街男孩)。 大中央调度:我们如何使用它? 如前所述,GCD为我们提供了调度队列,用于管理代码块。 这些分派队列负责处理我们提供给GCD的任务,并按FIFO ( 先进先出 )顺序进行处理(第一个任务排在队列的首位,依此类推)。 您还记得在串行调度队列中 ,一次只执行一个代码块。 在GCD中,这些块的执行时间在GCD的控制之下。 换句话说,我不知道代码的第1、2和3块要花多长时间,但是我知道两件事:1)每个块一次要运行一个,2)他们要运行一次以添加它们的顺序运行。 对于并发调度队列,可以同时执行多个代码块。 GCD的工作是决定何时启动每个块。 由于这些是同时运行的多个代码块,并且将重复执行这些代码块,因此GCD 决定何时这些块之一应在其他内核上运行 (一个内核是计算机的处理器之一。我的Macbook […]

iOS并发介绍

通过 谢尔盖·沙巴林 ( Sergey Shabalin) 假设您有一个运行在主线程上的移动应用程序,负责执行操作UI的代码。 每当您在应用中打包一些耗时的代码(例如从Web上下载或在主线程上进行图像处理)时,这都会显着降低UI性能,甚至可能导致其完全死机。 iOS中的多线程 那么有没有办法改变应用程序的架构,使此类问题永远不会发生? 我相信并发在这里可以提供帮助,因为并发能够执行两个或多个独立任务,例如计算,从Web或驱动器上下载数据,图像处理等。 但是,这并不是免费的,随着并发性的引入,代码线程安全性可能会在执行中受到损害。 一旦允许任务同时执行,就为任务可以访问相同资源的问题做好准备,例如更改不同线程上的相同变量或访问已被其他任务阻止的资源。 这最终可能导致拆除在不同线程上使用的那些资源。 在iOS开发中,并发用于提高生产力和UI响应能力,这是由Thread,GCD(中央总调度)和Operation等多种工具提供的。 可以肯定地说,在Swift 3出现之前,强大的GCD框架正在使用C API,这为用户操作带来了很多隐患。 Swift 3改变了一切。 GCD获得了一种基于GCD逻辑的易于使用的新语法。 为了更好地了解如何使用并发,让我们找出哪些关键概念与GCD和Operation工具一起使用。 基本概念是队列 。 因此,从开发人员的角度谈论iOS并发时,很可能会提到队列。 队列中有闭合的队列,并根据指定的顺序,系统将它们一个接一个地拉,然后将它们部署在适当的线程上。 队列在构成并发模式的多个线程中遵循FIFO(先进先出)原则。 串行队列与并发队列 队列可以是: 串行或连续,当队列顶部的闭包被iOS拉出并运行直到结束时,再拉出另一个队列元素,依此类推。 并发或多线程,当系统在队列顶部拉一个闭包并在某个线程中启动其执行时。 如果系统有权访问更多资源,则它将从队列中选择下一个元素,并在第一个功能仍在工作时在另一个线程上启动它。 这样,系统可以提取许多功能。 同步方法与异步方法 创建队列后,有两种将作业放入队列的方法: 同步方法表示与当前队列有关的同步任务放置。 整个方法完成后, sync方法将控制权返回到当前队列,从而阻止了当前队列。 异步方法是与当前队列相关的异步任务布置。 与sync方法相反, Async在另一个队列上启动任务后立即将控件返回到当前队列,而无需等待其结束。 因此, 异步方法不会阻止当前队列上的任务执行。 可能会出现以下不同的队列 : 如果是异步执行方法, 则为 串行队列;如果是同步执行, 则为并发队列。 并发队列。 开发人员的任务完全取决于队列选择,并在同步方法的帮助下将任务(封闭)同步添加到队列中,或通过异步方法异步添加到队列中。 iOS从那里获取它。 全局调度队列 除了定制的用户队列之外,iOS还提供了一些五种现成的全局调度队列: 主队列。 负责所有UI操作: […]