Tag: 并发

在Swift中使用NSOperation和NSOperationQueue在UICollectionView中进行延迟加载

在Flickr上创建帐户后,您必须生成api密钥并将该api密钥替换为搜索URL中的“ ”。 由于这不是有关Flickr服务的教程,因此我们将不再赘述。 搜索Flickr之前,需要输入API密钥。 与Flickr api调用和图像数据相关的类已经创建并包含在项目中,该项目的链接在本教程的最后提供。 在本教程中无法编写和解释整个代码,请参考所有代码片段以从项目中提供的类中获得详细的了解。 该项目包包含两个类和一个专用于执行基于Flickr的任务的结构: FlickrSearchResults :一种结构,用于包装搜索词和为该搜索找到的结果。 FlickrPhoto :从Flickr检索的照片的数据模型-其元数据信息,例如其ID。 还有一种构建Flickr URL和一些大小计算的方法。 FlickrSearchResults包含这些对象的数组。 Flickr :提供一个简单的基于块的API来执行搜索并返回FlickrSearchResult 随意看一下代码-这很简单,可能会激发您在自己的项目中使用Flickr! 打开Flickr.swift并将apiKey的值替换为apiKey获得的API密钥。 它看起来应该像这样: 当您准备好开始时,请转到下一部分-是时候进行一些准备工作,然后再熟悉Flickr。 准备图像数据 打开SearchViewController.swift ,您将在其中找到两个属性: var searchs = [FlickrSearchResults]() 让 flickr = Flickr() searches是一个数组,将跟踪应用程序中进行的所有搜索,而flickr是对将为您进行搜索的对象的引用。 创建图像下载管理器 ImageDownloadManager类将创建一个单例实例,并具有NSCache实例来缓存已下载的图像。

转向漂亮的异步Swift代码

本文提高了对与异步代码相关的问题的认识,并提供了在Swift 3.0–3.1上进行编程时解决这些问题的示例。 样本问题的描述 这是源数据: Person是包含有关人员信息的结构的示例。 MyService是用作模型入口点的类的示例。 MyViewController是管理与UI相关的实例的类的示例。 MyService必须将Person提供给MyViewController以返回具有相应标识符的请求。 它可能在内存中没有所请求的信息,因此获取人员数据可能涉及网络,磁盘操作等。 回到同步编码时代 我注意到许多项目仍然使用同步方法。 因此,让我们首先使用它来解决示例问题。 扩展MyService { func person(identifier:String)throws-> Person? { 返回/ *从网络获取人员* / } } 似乎非常简单: input arguments -> output result 。 此方法可以返回该人(如果没有这样的人,则为nil);如果出了问题,则抛出一个问题。 它的使用方式就是这样: 扩展MyViewController { func present(personWithID标识符:字符串){ / *不要忘记调度到后台队列* / DispatchQueue.global()。async { 做{ 让人=尝试self.myService .person(标识符:标识符) / *不要忘记调度到主队列* / DispatchQueue.main.async { self.present(人:人) } } { / *不要忘记调度到主队列* […]

iOS并发-真相

在开发iOS应用时,无法逃避并发或线程问题。 在许多情况下,都非常需要线程,队列和并发的概念。 这些泛泛的概念确实非常广泛,但是对于苹果公司而言,在开发应用程序时,它本身就可以管理很多线程方面的事情。 首先让我们谈一下并发性—真正的意义是什么,为什么会如此重要。 确切地说, 并发是由CPU处理多个任务 ,仅此而已,没有什么大不容易理解的词:)。 早期的Apple设备安装了1个CPU,可以在多个任务之间进行上下文切换。 2011年晚些时候,iPad和iPhone升级为双核,是的,这是一个很大的举措。 苹果提供了可以用于执行简单任务的GCD(Grand Central Dispatch)和通常用于执行复杂任务的操作队列。 之所以需要并发专业知识的原因之一是保持UI响应能力,这可以通过以下示例来理解:在UITableView中需要滚动图像,而其下载和转换图像则需要时间并且有点慢,因此建议这样做在除主线程之外的其他线程上运行这些任务。 注意:默认情况下,所有UI更新任务都在主线程上执行。 如果我们在主线程上执行繁重或复杂的计算,那么它将导致您的应用程序的UI阻塞并使其无响应。 修改或更新相同资源的任务一定不能同时运行,我们需要确保资源是线程安全的。 术语“任务”和“过程”通常可以互换使用。 不幸的是,术语“多任务”通常用来表示一次管理多个进程的能力,而“多处理”是指具有多个处理器(CPU)的系统。 我们可以创建自己的线程,但是在没有专业知识的情况下使用此选项可能会导致很多问题。 在将任务分配给GCD或操作队列时,系统将自己管理线程。 我们可以创建多个任务并将其分配给队列。 每个任务分为多个单元,每个单元由可用线程执行。 系统将任务分解为多个单元并将其分配给线程。 在下面给出的图中,我们有一个包含6个任务的队列,该队列分为2个线程,“线程1”执行4个任务(紫色,黄色,红色和绿色),“线程2”执行2个任务(蓝色,橙色)。 在GDC和Operation队列之间进行选择取决于我们希望任务之间或与主线程进行通信的方式,因为两者都提供了使同步函数异步运行的各种方法。 一个人在处理并发时可能会面临3个主要潜在问题,而前两个可以通过使用GCD或Operation队列来解决: 比赛条件 优先级倒置 死锁 竞争条件:当两个线程尝试同时访问或更改同一资源时。 这完全取决于线程的调度方式以及它们的启动,睡眠和恢复时间。 Xcode 8具有Thread Sanitizer或TSan来找出潜在的竞争条件代码。 下图显示了通过两个线程访问资源的理想情况,其中线程在不同的时钟周期访问值以进行读/写。 但是有可能在线程2的写操作之前完成了线程2的初始化和读取操作,这在线程2的写语句之后给出了不一致的结果。 这可以通过下面给出的图表来理解。 竞争条件可以通过锁定将使另一个线程等待其完成工作的值来解决,也可以通过将串行任务一次限制在一个特定资源中来使用串行队列来解决。 优先级倒置: 当高优先级工作变得依赖于低优先级工作时,或者成为低优先级工作的结果时,就会发生优先级倒置 。 结果,可能会发生阻塞,旋转和轮询。 在同步工作的情况下,系统将尝试通过在反转期间提高较低优先级工作的QoS(服务质量)来自动解决优先级反转。 在以下情况下会发生这种情况: 当为串行队列上的块调用dispatch_sync()和dispatch_wait() 。 当互斥量由具有较低QoS的线程持有时,调用pthread_mutex_lock()时。 在这种情况下,将持有锁的线程提升到调用方的QoS。 但是,跨多个锁不会发生这种QoS提升。 在异步工作的情况下,系统将尝试解决串行队列上发生的优先级倒置。 让我们考虑一个示例,其中存在3个任务– task1,task2和task3,它们分别具有低,中和高优先级,它们需要一个公共资源。 首先, task1开始执行并锁定资源,而task2启动并暂停task1,但是由于它被task1锁定,它将无法访问公共资源,同时task3启动并暂停task2并开始执行,因为它具有较高的优先级,但是当它需要公用资源,因为它仍被task1锁定,因此它被阻塞。 在这种情况下,低优先级任务和中优先级任务被高优先级任务停止,而需要公共资源的高优先级任务被低优先级任务停止,这是优先级倒置的经典情况,如下所示: […]

将iOS和Mac异步和相关任务封装到Cocoa Operation子类中

异步任务(例如,从网络中获取数据,解析,处理数据并将数据保存到本地缓存中)是当今应用程序执行的常规任务。 作为开发人员,我们必须确保UI /主线程运行平稳,并将长时间运行的繁重工作任务移入后台线程,以维持60 FPS动画。 Apple为开发人员提供了两种在后台线程中执行任务的方式: 大中央调度(GCD):开发人员可以使用队列在后台线程池中串行或并发执行任务的API集。 操作(也称为NSOperation):可可抽象类,代表要执行的单个任务单元。 它是一个线程安全类,具有开箱即用的内置状态,优先级和QoS,取消和依赖项管理。 在本文中,我们将构建一个异步的Operation子类,该子类可从GitHub API异步获取存储库,以及一个从属的Operation子类,可将获取的存储库数据解析并将其序列化为Swift类。 我们将建立什么 AsynchronousOperation:支持异步操作的Operation子类。 FetchRepoOperation:AsynchronousOperation子类,该类从上周开始使用URLSession异步获取最新趋势的GitHub存储库的数据。 ParseRepoDataOperation:操作子类,该子类使用Swift Codable和JSONDecoder将FetchRepoOperation中的数据解码并序列化为GithubRepo对象的数组。 游乐场页面:使用OperationQueue执行操作,在操作之间添加依赖关系,并使用完成块在操作对象之间传递数据。 使用Operation子类实现异步操作 默认情况下,Operation Class同步运行代码。 Apple提供了一种通过子类化isAsynchronous布尔属性并将其重写为true来异步运行代码的方法。 我们还需要使用枚举添加我们自己的状态管理属性,处理从就绪,执行和完成状态的更改。 当同时读写状态属性时,dispacth队列将用于对状态属性使用调度屏障来处理同步。 在启动函数中,我们检查任务是否未取消,如果取消,则仅调用finish将状态更改为完成并返回。 如果没有,我们将状态设置为执行并调用主函数。 我们的子类将覆盖main函数,以执行函数内部的任务。 实现FetchRepoOperation来获取Github API FetchRepoOperation是异步操作的子类,我们声明两个可选属性,fetchedData是一个Data对象,将用于存储来自API调用的数据响应,以及一个error属性,将用于存储来自API的错误如果发生,请致电。 在超类的重写的main方法中,我们构造URL和查询项,这些查询项将查询自上周以来创建的存储库,该存储库按星数降序排列。 之后,我们初始化URLRequest并使用URLSession调用异步数据任务。 在数据任务完成处理程序内部,我们将响应数据和错误分配给实例属性,然后调用finish方法将操作的状态设置为finish,以将操作标记为完成。 实施ParseRepoOperation以使用Swift Codable类解码JSON数据 我们创建GithubRepoFetchResult,GithubRepo,GithubOwner Swift类,该类实现了codable和CodingKeys枚举,以将json属性名称映射到实例属性骆驼案例名称。 通过使用Codable,我们可以利用JSONDecoder将Data解码为自动实现Codable的类。 实现ParseRepoOperation非常简单,我们将Operation用作子类,因为JSONDecoder的解码功能是同步的,因此我们不需要使用AsynchronousOperation。 我们声明了3个可选的实例属性,fetchedData是从FetchRepoOperation传递的数据,如果将数据解码为对象时发生错误,则错误为Error对象,包含GitHubRepo的repos数组将用于将JSONDecoding的结果存储到对象中。 在main函数内部,我们使用guard来解包可选的fetchedData,如果为nil,我们只是从函数中返回。 之后,在try catch块中,我们使用JSONDecoder解码函数,将fetchedData和GithubRepoFetchResult作为要解码的根类。 然后,我们将GithubRepoFetchResult中的items属性分配给repos实例属性。 如果解码时发生错误,我们会将错误分配给我们的错误实例属性。 使用OperationQueue执行操作 为了执行操作,我们使用OperationQueue,它充当优先队列,该队列使用先进先出机制来处理操作的执行。 我们将maxConcurrentOperationCount设置为1,因此我们的操作不会同时执行。 我们实例化FetchRepoOperation和ParseRepoOperation对象,然后将FetchRepoOperation对象添加为ParseRepoOperation对象的依赖项,因此将首先启动fetch任务,并且必须完成分析任务才能开始。 在操作之间传递数据并不容易,有许多方法可以做到,例如使用包含数据的数据包装器引用类,然后将其传递给每个操作。 对于此实现,我们将使用操作完成块,该操作将在操作完成时调用。 我们参考解析和提取操作对象为提取操作完成块分配一个闭包。 使用Unowned来避免保留周期,在该块内部,我们将获取响应数据传递给了解析的fetchedData属性。 我们为解析操作完成块属性分配一个闭包,该闭包仅循环存储库并将打印库的名称打印到控制台,以便我们看到结果。 最后,要开始操作,我们将调用包含获取和解析操作的数组传递给OperationQueue的addOperations来开始任务。 结论 可可操作类为开发人员提供了极大的灵活性,例如任务之间的依赖性,调整队列优先级和QoS,执行后台任务时的取消和状态管理。 […]

iOS开发课程:调度

如何并发执行代码? 如何管理调度工作单位? 让我们在快速指南中使用Dispatch框架创建一个简单的项目! 调度包括语言功能,运行时库和系统增强功能,这些系统功能全面,全面地改进了对macOS,iOS,watchOS和tvOS中多核硬件上的并发代码执行的支持。 BSD子系统,Core Foundation和Cocoa API均已扩展为使用这些增强功能,以帮助系统和您的应用程序更快,更高效地运行,并提高响应速度。 考虑单个应用程序有效地使用多个内核有多么困难,更不用说在具有不同数量计算内核的不同计算机上或在多个应用程序竞争那些内核的环境中进行操作了。 在系统级别运行的GCD可以更好地满足所有正在运行的应用程序的需求,并以平衡的方式将它们与可用的系统资源进行匹配。 阅读有关Apple Developer的更多信息 每个工作项都可以同步或异步执行。 当工作项与sync方法同步执行时,程序将等到执行完成后再返回方法调用。 当使用async方法异步执行工作项时,该方法调用立即返回。 通过同步 / 异步执行在操场上运行以下代码 分派队列可以是串行的 ,以便一次执行一个工作项,也可以是并发的 ,以使工作项按顺序出队,但可以一次全部运行并且可以按任何顺序完成。 串行队列和并发队列均以先进先出(FIFO)顺序处理工作项。 在操场上修改代码,然后再次运行。 现在我们有一个并发队列。 *默认为串行 启动应用程序时,系统会自动创建一个称为main queue的特殊队列 。 排队到主队列中的工作项在应用程序的主线程上顺序执行。 您可以使用main type属性访问主队列。 重要 尝试在主队列上同步执行工作项会导致死锁。 在操场上运行代码。 那演示了如何从background在主队列上执行代码。 除了串行主队列之外,系统还创建了许多全局并发调度队列。 您可以使用global(attributes :)类型方法访问与指定服务质量(QoS)最匹配的全局并发队列。 该代码演示了使用不同qos模式的队列初始化。 工作项使您可以直接配置各个工作单元的属性。 它们还使您可以为各个工作单元着想,以等待其完成,收到有关其完成的通知和/或取消它们的目的。 DispatchWorkItem封装了可以执行的工作。 可以将工作项分派到DispatchQueue和DispatchGroup 。 也可以将DispatchSource设置为DispatchSource事件,注册或取消处理程序。 使用WorkItem管理演示程序在操场上运行代码。 分组块允许聚合同步。 您的应用程序可以提交多个块并跟踪它们何时完成,即使它们可能在不同的队列上运行也是如此。 如果在完成所有指定任务之前无法取得进展,此行为将很有帮助。 DispatchGroup允许工作的聚合同步。 您可以使用它们来提交多个不同的工作项,并跟踪它们的完成时间,即使它们可能在不同的队列中运行。 如果在完成所有指定任务之前无法取得进展,此行为将很有帮助。 你完成了! 拍! 拍! 接下来是什么? […]

NS for iOS Devs —并发

在Swift Post上以更好的格式阅读此文章。 在专注于iOS中的Application Lifecycle和View Lifecyle之后,我们的下一个主题是concurrency 。 在当今世界,我们面临着同时处理多个操作的挑战。 我们希望缩短用户的等待时间,即使这并非总是必要的。 长时间显示负载或冻结UI会转移用户的注意力,并会不时减少应用程序的使用。 同时,保持注意力在应用程序中变得非常困难。 因此,我们不必并行运行操作,而必须并行运行它们,以获得更好的用户体验。 为什么我们需要非常了解并发性? 如今,几乎每个应用程序都可以进行在线数据传输(网络请求和响应)。 正确处理网络操作非常重要。 除了应用程序功能之外,我们不想在使用应用程序时打扰或阻止用户。 中断总是会导致不良的用户体验。 因此,当我们显示任何类型的UI时,我们通常同时处理网络请求。 我们可以在显示加载动画的同时发送网络请求并等待响应。 并发由iOS中的不同API处理。 我们将重点介绍基本的Grand Central Dispatch(GCD),以了解并发性。 在编程世界中,并发是一个大话题,并且具有至关重要的地位。 如果您想了解有关并发的更多信息,有关Quora的答案很好解释。 在谈论DispatchQueue之前,我们应该首先了解队列。 因为DispatchQueue可以在不同的队列上运行操作。 我们到底需要了解什么队列? iOS中有两种类型的队列,即主队列和其他队列。 我们只有一个主队列,但是我们可以有多个后台队列,它们可以在其上运行不同的操作。 后台队列根据其优先级在线程池上运行。 但是主队列在主线程上运行。 所有UI(用户界面)更新都必须在主线程上运行。 因此,我们将主队列用于UI。 最重要的是这里只有一个主队列 。 主队列应合理使用。 由于每个UI更新都必须在主队列中完成,因此,如果我们对其他每个操作都使用主队列,则可以冻结UI操作。 例如,如果我们在主队列上发送网络请求并根据响应更新用户界面,我们将看到用户界面直到返回数据才响应。 提示是我们应该尽可能多地使用后台队列,并尝试将主队列留空用于非UI操作。 我们如何使用带有简单网络请求和GCD的队列? 发送网络请求非常简单。 URLSession是用于简单网络请求的最常用方法。 我们可以通过dataTask发送请求,并在闭包中获取带有错误(如果有)的响应数据。 最后,我们可以看到具有更好用户体验处理的​​示例代码。 下面的代码从后台队列开始,当需要在屏幕上显示加载指示器时,我们切换到主队列。 执行继续并启动任务。 收到响应后,我们仍在后台队列中。 如果出现错误,现在我们将切换到主队列并显示警报。 主线程与主队列以及它们为什么不相同可能会引起混淆。 有一个主线程和一个主队列。 如果您对更多细节感到好奇,可以阅读这篇文章。 我们已经讨论了一个简单的网络请求和GCD。 对于许多应用程序而言,同时处理更复杂的任务很重要,而GCD仅提供一种简单而直接的解决方案。 我们可能需要取消并发操作或遵循其状态。 GCD没有提供解决这些问题的方法。 […]

掌握CoreData(第14部分,多线程并发策略,父级-子级上下文)

父/子管理对象上下文 如图1所示,自iOS 6起,有一个更好,更优雅的策略。 父/子托管对象上下文的概念是, 子托管对象上下文依赖于其父托管对象上下文 将其更改保存到相应的持久性存储中。 实际上, 子托管对象上下文无法访问持久性存储协调器 。 每当保存子托管对象上下文时, 它包含的更改将被推送到父托管对象上下文 。 无需使用通知即可将更改手动合并到主或父托管对象上下文中。托管对象上下文可以嵌套。 子托管对象上下文可以具有自己的子托管对象上下文。 相同的规则适用。 此方法由Apply推荐 连接到持久性存储协调器的上下文应该是真理的唯一来源,这意味着应该在主线程上创建它,它负责处理与UI相关的任务。 如何实现并发 您可以看到如图2所示的简单并发体系结构。应注意的几点 子上下文负责使用某些后台线程执行繁重的任务 由于子上下文正在使用后台线程,因此此繁重的任务不会阻止主线程。 父NSMainQueueConcurrencyType应该在NSMainQueueConcurrencyType上创建 子NSPrivateQueueConcurrencyType是在NSPrivateQueueConcurrencyType上NSPrivateQueueConcurrencyType 当子项ManagedObjectContext完成繁重的任务时,核心数据也会自动将更改合并到父项中,我们只需要仅在子项上调用save方法 子ManagedObjectContext也可以在其中包含其他子上下文 使用此策略回顾上一部分 创建子托管对象上下文与到目前为止所看到的稍有不同。 子托管对象上下文使用不同的初始化程序initWithConcurrencyType:。 初始化程序接受的并发类型定义了托管对象上下文的线程模型。 NSMainQueueConcurrencyType →受管对象上下文只能从主线程访问。 如果尝试从任何其他线程访问它,则会引发异常。 NSPrivateQueueConcurrencyTypea →创建并发类型为NSPrivateQueueConcurrencyType的托管对象上下文时,该托管对象上下文与私有队列相关联,并且只能从该私有队列进行访问。 Apple引入父/子托管对象时,Core Data框架中添加了两种关键方法 上下文, perform :和performAndWait :这两种方法都可以使您的生活更加轻松。 在托管对象上下文上调用perform :并传入要执行的代码块时,Core Data确保该块在正确的线程上执行。 对于NSPrivateQueueConcurrencyType并发类型,这意味着将在该受管对象上下文的专用队列上执行该块。 数据流 如图3所示。“用户列表”屏幕显示了屏幕中的所有用户。 它从主线程上的父上下文中获取用户数据。 由于它的唯一目的是与UI进行交互,因此此上下文无法执行任何繁重的处理任务。 如图3所示,当前数据库中只有4个用户 现在UserListScreenViewController需要来自服务器的数据。 它打入网络电话,新的996用户响应。 它将在该线程上进行解析,并且不会影响主线程 。 用户仍然可以与UI交互,因为我们没有阻止主线程 。 […]

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

本文是Swift系列并行编程的第3部分。 在第1部分中,我们研究了Dispatch Queue和系统提供的队列。 在第2部分中,我们重点介绍了定义任务和GCD提供的强大API的另一种方法。 在这篇文章中,让我们看一下Operation API和与Grand Central Dispatch相比提供的灵活性。 如果要查看系列的所有部分: 并发和GCD — Swift并行编程— 1/4 GCD —使用Swift进行并行编程—第2/4部分 操作和操作队列概述—使用Swift并行编程—第3/4部分 操作是一种面向对象的方式,用于封装需要异步执行的工作。 Operation代表单个工作单元。 它是一个抽象类,提供了一个有用的,线程安全的结构,用于对状态,优先级,依赖关系和管理进行建模。 — NSHipster 由于操作是抽象类,因此您不能直接使用它。 Foundation提供了两个系统定义的子类InvocationOperation和BlockOperation来执行任务。 就像在定义的子类中所说的那样,您只需要关注任务的实际实现即可。 一个操作只执行一次任务,因此不能再次执行它。 您可以通过将操作添加到操作队列中来执行操作。 操作队列可以通过在辅助线程上运行它们来直接执行其操作,也可以使用libdispatch库(即Grand Central Dispatch)间接地执行其操作。 因此, Operation API是Grand Central Dispatch的更高级别的抽象。 您可能想知道:什么任务? 🤔 让我们讨论以下术语: 任务 :需要完成的一项工作。 进程 :可执行代码块,可以由多个线程组成。 流程是您的应用程序的实例。 每当您执行应用程序的冷启动时,Process ID都会获得更改,而在您执行热启动时,它保持不变。 它包含执行应用程序所需的所有内容,其中包括堆栈,堆和所有其他资源。 线程 :操作系统提供的一种机制,它允许多个指令集在单个应用程序中同时运行。 与进程相比,线程与其父进程共享其内存。 这可能会导致出现问题,例如让两个线程同时更改资源(例如,变量)。 线程是iOS上的有限资源。 一个进程最多只能同时有64个线程,而在任何文档中都没有正式提及,因为它会根据上下文和内核数量而有所不同。 操作状态: 操作对象在内部维护状态,以确定何时可以安全执行,并在操作的整个生命周期内将进度通知给外部客户。 isReady:在准备好执行操作时通知客户端。 当操作准备好立即执行时,此键路径返回true […]

掌握CoreData(第13部分,多线程并发策略通知)

Core Data支持两种流行的策略,即通知和父子托管对象上下文。 在本部分中,我们将仅查看Notifications。Apple不建议使用此策略,因此我们将不做进一步的介绍。 通知类型 托管对象上下文发布了三种类型的通知,以通知对象在托管对象上下文中发生的更改 NSManagedObjectContextObjectsDidChangeNotification:当托管对象上下文的一个托管对象已更改时,将发布此通知 NSManagedObjectContextWillSaveNotification:在托管对象上下文执行保存操作之前发布此通知 NSManagedObjectContextDidSaveNotification:在托管对象上下文执行保存操作后发布此通知 注意:仅当您在上下文上调用save()方法时,这些通知才会发布。 当您调用save()方法WillSaveNotification和NDidSaveNotification通知 肯定会调用,而DidChangeNotification仅在您实际在上下文中进行了某些更改时才调用。 当托管对象上下文通过持久性存储协调器将其更改保存到持久性存储时,其他托管对象上下文可能希望了解这些更改。 这很容易做到,甚至更容易将更改包含或合并到另一个托管对象上下文中。 注意:如果您正在执行NSBatchUpdateRequest / NSBatchDeleteRequest,这些通知将不会触发( 暂时忽略) 处理通知 在此处下载启动项目,如果您已经先删除了该应用程序 我们可以通过在NotificationCenter添加观察者来观察这些通知,如图1所示。 如您在图2中看到的,我们处理了DidChangeNotification 只。 notifications只是整个故事的一半。 如您所见,所有观察者方法都是通过通知对象传递的。 通知对象具有一个userInfo实例属性,该属性在Dictionary中保存所有信息。 这些都是用于检索更改的数据的键 NSUpdatedObjectsKey密钥包含所有更新的NSManagedObject 。 NSInsertedObjectsKey密钥包含所有插入的NSManagedObject 。 NSDeletedObjectsKey密钥包含所有已删除的NSManagedObject 。 NSRefreshedObjectsKey密钥包含所有刷新的NSManagedObject 。 插入盒 如您所见,我们在图3中插入了User对象。 执行save()方法时将触发通知 正如您在图4中看到的那样,由于我们在图3中插入了对象,这意味着将首先DidChangeNotification上下文中的更改,而DidChangeNotification将触发,并且我们对此进行了处理,这就是在控制台中打印出插入对象的原因。 之后, WillSaveNotification和DidSaveNotification通知将依次触发,并且由于我们未在选择器方法中执行任何操作,因此将不会发生任何事情。 如图4所示,我们在图3中插入的用户被打印在控制台上。 更新案例 如图5所示,我们获取了刚才插入的User对象,并在我们调用save()方法时更新了它的值。Notification handler打印了Updated值。 我们通过执行以下操作来完成此操作 从appdelegate单例对象引用persistentContainer 从persistentContainer创建/访问单例托管对象上下文 添加了托管对象上下文观察器以侦听上下文事件 我们刚刚从持久性存储中插入的Fetched User并更新了它的firstName和secondName属性,如图5所示。 在主上下文中使用save方法执行对持久性存储的提交 通知DidChangeNotification 方法在控制台中打印更新的值 之后, WillSaveNotification和DidSaveNotification通知将依次触发,并且由于我们未在选择器方法中执行任何操作,因此将不会发生任何事情 […]

NSOperation和NSOperationQueue可以改善iOS中的并发性

通过谢尔盖·沙巴林(Sergey Shabalin) 作为我的文章“ iOS并发性简介”的后续,今天,我想分享我对如何提高iOS应用程序中的并发性水平的看法。 操作可以并发提供帮助。 操作是作业封装的一种面向对象的方法,该方法可以异步完成。 应该将操作与操作队列结合使用或独立使用。 操作对象是用于所需作业封装的Operation类实例。 Operation类本身是一个抽象类,出于实用程序的目的,该类将由子类实现。 让队列= OperationQueue() queue.maxConcurrentOperationCount = 2 let operation1 = BlockOperation(block:{ … }) operation1.qualityOfService = .userInitiated let operation2 = BlockOperation(block:{ … }) operation1.completionBlock = { … } operation2.completionBlock = { … } operation2.addDependency(operation1) queue.addOperation(operation1) queue.addOperation(operation2) OperationQueue类调节操作对象集的执行。 添加到队列后,该操作将保留在该队列中,直到完成或取消。 队列中的操作根据其优先级和操作之间的依赖性进行自组织。 如果队列中的操作具有相似的优先级,则按照FIFO原理执行它们。 操作支持以下基本特征: 依赖关系,防止操作在之前的操作完成之前开始。 支持附加完成块。 使用KVO监视操作状态的变化。 支持操作优先级并影响其执行顺序。 取消选项,允许在执行时停止操作。 如果操作不能完全满足应用程序的需求,则可以创建NSOperation子类以添加缺少的功能。 进口基金会 class […]