掌握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将触发,并且我们对此进行了处理,这就是在控制台中打印出插入对象的原因。

之后, WillSaveNotificationDidSaveNotification通知将依次触发,并且由于我们未在选择器方法中执行任何操作,因此将不会发生任何事情。 如图4所示,我们在图3中插入的用户被打印在控制台上。

更新案例

如图5所示,我们获取了刚才插入的User对象,并在我们调用save()方法时更新了它的值。Notification handler打印了Updated值。 我们通过执行以下操作来完成此操作

  • 从appdelegate单例对象引用persistentContainer
  • 从persistentContainer创建/访问单例托管对象上下文
  • 添加了托管对象上下文观察器以侦听上下文事件
  • 我们刚刚从持久性存储中插入的Fetched User并更新了它的firstNamesecondName属性,如图5所示。
  • 在主上下文中使用save方法执行对持久性存储的提交
  • 通知DidChangeNotification 方法在控制台中打印更新的值
  • 之后, WillSaveNotificationDidSaveNotification通知将依次触发,并且由于我们未在选择器方法中执行任何操作,因此将不会发生任何事情

我们不会delete 。 您可以相信它会起作用。 现在的问题是,我们通知了哪些内容已被更新,插入和删除,但是我们如何也可以合并或更新其他上下文。 在下一节中,我们将要执行此操作

将更改合并到另一个上下文

正如我们在第12部分中看到的那样,我们遇到了并发问题,其中一个上下文正在后台线程中执行一些繁重的任务,而另一个正在处理与UI相关的工作,但是两者都没有意识到工作状态,无论它是否完成,我们都看到了上下文将数据保存到持久性存储中,并使用其他导致问题的旧值。 因此,在本节中,我们将研究通知如何解决此问题,并在完成任务后将更新后的值合并到其他上下文中。 简而言之,一个上下文如何通过该上下文所做的更改来更新其他上下文。

假设我们有两个托管对象上下文,分别称为托管对象上下文A托管对象上下文B,它们都从持久性存储中请求用户数据,如图6所示。用户实体具有firstName secondName 属性。

受管对象上下文B正在处理服务器数据的私有上下文

代表UI数据主线程的托管对象上下文A

如图7所示, 受管对象上下文B使用来自服务器的数据更新了firstName值,并使用上下文上的save方法将其更改为持久性存储协调器持久性存储 受管对象上下文A不知道仍在更改并且具有较旧的值,这是一个问题

如图8所示,如果我们按照上一节中的说明处理通知,那么在执行save操作后,我们现在可以观察到NSManagedObjectContextDidSave通知, NSManagedObjectContextDidSave选择器将被执行,我们可以调用mergeChanges方法,该方法将更新ManagedObjectContextA,因此可以使用上下文通知可以解决这个问题。

现在,两个上下文将具有相同的状态。 合并时可能会发生冲突,稍后我们将讨论这些冲突。

现在,让我们深入研究编码部分,并使用代码进行验证,但在开始之前首先删除该应用程序。 我们添加了用户对象,并将其保存到持久性存储中,如图7所示。

当我们刚刚插入私有上下文更新值时,使用通知,我们也将更改合并到主上下文中,您可以在控制台上看到图9。 我们在完成许多任务后完成了此任务

  1. 添加了NSManagedObjectContextDidSave观察者,以侦听私有上下文中的发布后事件
  2. 在私有上下文中获取了User对象,并在该上下文中更新了firstName值。 注意:主上下文不知道私有上下文所做的更改
  3. 调用save方法将更新后的用户推送到持久性存储
  4. 控制台上的已打印子上下文用户对象更新值正在反映
  5. save方法完成后,它将发布我们观察到的通知,即NSManagedObjectContextDidSave并调用选择器方法来执行我们定义的操作contextDidSave(_ notification: Notification)
  6. contextDidSave(_ notification: Notification)我们使用上下文中可用的mergeChanges方法将私有上下文更改合并到主上下文中
  7. 现在,主上下文具有更新的值,如图8中的控制台所示。

注意:当我们调用mergeChanges方法时,可能会发生冲突,为此,我们需要创建合并策略,我们将在以后的部分中进行讨论。

缺点

  1. 复杂代码→增加错误
  2. 手动同步
  3. 大量的代码
  4. 受管对象上下文之间的交叉通信

摘要

在第13部分中,我们使用Core数据提供的Notification解决了并发问题。

接下来是什么?

在下一部分中,我们将研究如何使用核心数据提供的父子并发策略来解决并发问题。

有用的链接

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/Concurrency.html

https://medium.com/shakuro/introduction-to-ios-concurrency-a5db1cf18fa6

https://cocoacasts.com/swift-and-cocoa-fundamentals-threads-queues-and-concurrency
https://developer.apple.com/documentation/coredata/using_core_data_in_the_background

https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext

https://medium.com/@marcosantadev/core-data-notifications-with-swift-acc8232a674e

https://marcosantadev.com/core-data-notification-swift/

https://www.youtube.com/watch?v=_QolYhiKWvU