iOS CoreData多Context多线程使用及设计方式

    IOS9开始,创建 NSManagedObjectContext 有两种方式:NSMainQueueConcurrencyType 和 NSPrivateQueueConcurrencyType:

    NSMainQueueConcurrencyType 的 context 在主线程中运行,用于响应 UI 事件,
    NSPrivateQueueConcurrencyType 的 context 用于处理耗时任务的操作。

    对于耗时的任务,比如保存大量数据时,我们希望使用辅助线程处理,而不在主线程中,防止造成用户界面卡顿,常见的思路有两种:

    1. 建立具有上下级关系的多个 context; 注意: parent context中的managedObject发生变化时,会马上同步给child context中,但child context中的managedObject发生变化时,不会同步给parent,必须调用child context的save!
    2. 建立独立的并列关系的 context。注意: 并列关系的context中的managedObject变化时,互相独立,即使save,其他context中的managedObject也不会更变,需要通过通知处理,下文详细说明。

    后文会介绍两种方式的具体实现方式,而首先需要注意的是,无论采用那种方式,都需要使用多个 context,不同的 context 可能会在不同的线程来操作,但并不能直接将 context 放到 dispatch queue 或 NSThread 等的 block 中去执行。那应该怎么用呢?当一个 context 被创建时,它自身会绑定一个队列,要在不同线程中使用 context 时,不需要我们创建后台线程然后访问 context 进行操作,而是交给 context 自身绑定的队列去处理,我们只需要在下面两个方法的 Block 中执行操作即可:

     func performBlock(_ block: () -> Void)         //在队列中异步地执行 Blcok
     func performBlockAndWait(_ block: () -> Void)  //在队列中同步执行 Block, 会产生阻塞

需要注意的是,你可以在其他线程中调用 context,但是需要调用 context 的上面两个方法来执行操作,这两个方法会切换回自己的线程执行操作。

    下面介绍上面提到的两种思路的具体细节。
    采用多个 context 在多个线程中运作的设计方式主要有以下三种,前两种方式采用的是思路1,第三种使用的是思路2:

    1. persistentStoreCoordinator <- mainContext <- privateContext(需要IOS5以上)

    这种方式下 mainContext 与 psc 连接,而 privateContext.parent = mainContext,由于 privateContext 没有直接和 psc 连接,子线程 privateContext 执行 save 操作后,不会进行任何IO操作,而是将变动同步到 mainContext 中,然后在 mainContext 在 mainqueue 中执行 save 时才进行 IO 操作,将数据进行本地持久化,因此可在 privateContext 中进行一些耗时任务,而在 mainContext 中只进行UI相关的操作。
但由于最终数据持久化的 IO 操作必须通过 mainContext 在 main 线程中进行的,因此数据量大时对 UI 线程占用较大,可能产生阻塞。


    2. persistentStoreCoordinator <- backgroundContext <- mainContext <- privateContext(需要IOS5以上)

    这种方式下backgroundContext与psc连接,mainContext.parent = backgroundContext, privateContext.parent = mainContext,backgroundContext 负责 IO,而前两步在 privateContext 和 mainContext 中只进行 save,耗时较少,最后在后台进行数据持久化的任务交给了 backgoundContext。其实我在看到这种结构的时候一直有一个疑惑,就是为什么这种结构下还需要 privateContext? background 负责大批量数据变更和 IO,main 负责小量数据同步操作并响应UI不就够了吗?后来反应过来,例如在使用 NSFetchedResultsController 时,如果想要在后台处理过大批量数据后更新界面,只有 back 和 main 的方式就做不到了,因为 NSFetchedResultsController 绑定的一定是 mainContext,除非在 backContext 操作完后主动发通知,否则 NSFetchedResultsController 不会触发相关的代理方法。而加入 privateContext,当其 save 时,将变动同步给 mainContxt,就能解决这一问题。

    3.  persistentStoreCoordinator <- mainContext
         persistentStoreCoordinator <- privateContext

    这种方式下,每一个 context 都可以与 psc 进行交互,耗时任务完全放在 privateContext 中进行,涉及 UI 的放在 mainContext 中。但这种方式面临两个 context 同步问题,一个 context 的内容变更后,导致另一个 context 中与之有交集的部分不一致,需要手动处理:
    a). 需要添加 NSManagedObjectContextDidSaveNotification 通知,当一个 context 完成 save 后, 需要通知其他 Context 同步,调用 mergeChangesFromContextDidSaveNotification 将数据变动 merge 到当前 context 中。
    b). 提到 merge,必然会想到冲突,对于多个 context 对同一个数据进行了修改,在 save 时会冲突,原因是每一个 context 在 fetch 时会保存当前本地数据的快照,当 save 时会对比这个快照和当前状态的差异,如果没有差异则直接合入,如果有差异,说明其他 context 对该块数据进行过修改,存在冲突,这时,通过 mergePolicy 属性来进行 merge 的策略选择,具体 mergePolicy 属性这里就不列举了,可自行查阅文档。


参考资料:
https://www.jianshu.com/p/283e67ba12a3
https://www.cnblogs.com/breezemist/p/5110387.html
https://www.jianshu.com/p/29d92e3d0e70
https://www.jianshu.com/p/37ab8f336f76
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/Concurrency.html#//apple_ref/doc/uid/TP40001075-CH24-SW1

你可能感兴趣的:(IOS,Swift)