掌握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交互,因为我们没有阻止主线程 。 现在,在完成一些繁重的任务之后,与以前的通知策略相比,它需要将这些更改合并到父对象中,只需调用save方法即可完成 。
通过在子上下文上调用save
方法,所有更改将合并到父上下文中,并且这些更改将不会进入持久存储,直到主上下文也将调用save。 因此,保存方法可以将更改推上一层 。
从图5中可以看到,当子上下文完成其繁重的处理任务时,它仅通过调用save方法将更改合并到父级,并且父级将使用更新后的值来更新UI。 正如您在图5中看到的那样,正在发生许多事情
- 当子上下文完成所有处理后,它将通过调用
save
方法将所有更改合并到主上下文中 。 调用save方法后,所有更改将合并到父上下文中。 合并将在主线程上完成,因此合并时间(以毫秒为单位),此时我们的主线程将忙于此操作。 我们仅在后台线程上移动了处理和解析时间 - 当主要环境发生变化时。 现在,刷新UI将由
UserListScreenViewController
来完成,而不是在请求获取数据时由主要角色负责,它将返回更新的数据 - 那时, 持久性存储不知道新的用户对象,我们也需要在主上下文上调用
save
方法,然后这些更改也将推送到持久性存储 。 因此,我们需要调用两个save
方法将更改推送到持久性存储。 因此,使用这么多孩子的上下文,您需要仔细考虑。 大多数应用程序需要一个子上下文,该子上下文可以完成大多数并发任务
注意:如果需要并发,不要将子上下文当作一个单例,总是创建一个新的上下文。 否则有时会造成问题
编码证明
让我们先删除该应用程序。 如图6所示,我们向持久性存储添加了四个User 。
在图7中,我们使用代码证明了这一点。 为此,我们执行了许多任务
- 使用
persistentContainer
viewContext
属性获取主线程上下文的引用 - 使用
NSMangedObjectContext
init方法创建了私有托管对象上下文,然后我们使用上下文parent
属性将其作为主上下文的子级,如图7所示。因此,主上下文充当了私有上下文的父级。 您会看到一件事newBackgroundContext,
当创建私有上下文时newBackgroundContext,
我们没有使用newBackgroundContext,
。 我们将在以后讨论。 - 由于我们之前在持久性存储中存储了4个用户对象,因此两个新上下文都应打印为4个用户对象,并且状态与持久性存储相同,正如您在日志中所看到的那样
- 在
Child Managed Object Context
(Private Context)上,我们创建了996个来自服务器的用户对象。 主线程上下文仍然不知道后台线程中的那些对象,并且主线程仍在执行和进行用户交互工作,因为我们没有阻止它 - 现在,如果我们在两个上下文中仅打印用户,则私有打印1000个用户对象,而主上下文仍打印4 个对象,因为它不知道这些更改在子上下文中发生
- 当我们在
Child Managed Object Context
(私有上下文)上调用save
方法时,它将自动合并Parent Object Context
(主线程上下文)上的所有用户对象。 - 如您所见,上下文
Child Managed Object Context and Parent Object Context
现在具有相同的状态。 虽然持久性存储仍包含4个Users对象。 如果终止应用程序并打印对象,由于主上下文尚未调用save方法,它将打印4个用户对象。
注意: newBackgroundContext(如果在父子体系结构应用程序中创建私有上下文时使用)将崩溃。 我们将在后面的部分中讨论
接下来是什么?
在下一部分中,我们将研究如何使用核心数据提供的父子并发策略来解决实际应用程序并发问题。
有用的链接
https://code.tutsplus.com/tutorials/core-data-from-scratch-concurrency–cms-22131
https://www.raywenderlich.com/7586-multiple-managed-object-contexts-with-core-data-tutorial