第一步:搭建 Core Data 多线程环境
这个问题首先要解决的是搭建 Core Data 多线程环境。Core Data 对并发模式的支持非常完备,NSManagedObjectContext 的指定初始化方法中就指定了并发模式:

init(concurrencyType ct: NSManagedObjectContextConcurrencyType)

有三种模式:
1.NSConfinementConcurrencyType
这种模式是用于向后兼容的,使用这种模式时你应该保证只能在创建的线程里使用 context,然而这不容易得到保证。关于此模式的最新消息是 iOS 9 中它将被废弃,不推荐使用。
2.NSPrivateQueueConcurrencyType
在一个私有队列中创建并管理 context。
3.NSMainQueueConcurrencyType
其实这种模式与第2种模式比较相似,只不过 context 与主队列绑定,同时也因此与应用的 event loop 紧密相连。当 context 与 UI 更新相关的话就使用这种模式。

从 iOS 9 开始就剩下后面两种模式了,那么搭建多线程 Core Data 环境的方案一般如下,创建一个 NSMainQueueConcurrencyType 的 context 用于响应 UI 事件,其他涉及大量数据操作可能会阻塞 UI 的,就使用 NSPrivateQueueConcurrencyType 的 context。

let mainContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
let backgroundContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)

但需要注意的是「Core Data 使用线程或者序列化的队列来保护 managed objects 和 managed object context,因此 context 假设它的默认拥有者是它初始化时分配的线程或队列,你不能在某个线程中初始化一个 context 后传递给另外一个线程来使用它。」这段蹩脚的话是我从NSManagedObjectContext的文档翻译来的,意思就是说 managed object context 并非线程安全的,你不能随便地开启一个后台线程访问 managed object context 进行数据操作就管这叫支持多线程了,那么应该怎么做呢?官方文档《Using a Private Queue to Support Concurrency》为我们做了示范,在 private queue 的 context 中进行操作时,应该使用以下方法:

 func performBlock(_ block: () -> Void)//在私有队列中异步地执行 Blcok
func performBlockAndWait(_ block: () -> Void)//在私有队列中执行 Block 直至操作结束才返回

要在不同线程中使用 managed object context 时,不需要我们创建后台线程然后访问 managed object context 进行操作,而是交给 context 自身绑定的私有队列去处理,我们只需要在上述两个方法的 Block 中执行操作即可。事实上,你也可以在其他线程中来使用 context,但是要保证以上两个方法。而且,在 NSMainQueueConcurrencyType 的 context 中也应该使用这种方法执行操作,这样可以确保 context 本身在主线程中进行操作。

题外话,在构建多线程 context 时,经常会出现这样的局面:Multi-contexts vs Concurrency。前者可能有更加复杂的情况,在 iOS 5之后,context 可以指定父 context,persistent store coordinator 不再是其与 persistent store 联系的唯一选择。需要注意的是,子 context 的 fetch 和 save 操作都会交给父 context 来完成,对于子 context 的 save 操作,只会到达上一层的父 context 里,只有父 context 执行了 save 操作,子 context 中的变化才会提交到 persistent store 保存。这种子 context 适合在后台执行长时间操作,比如在后台里在子 context 里导入大量数据,在主线程的父 context 里更新进度。另外一种是平行的多 context。Concurrency 的特性也是在 iOS 5 后开始支持,这个特性减少了平行的多 context 的需求。关于这个话题,可以看这篇文章:《Concurrent Core Data Stacks – Performance Shootout》

第二步:数据的同步操作
总的来说,在多 context 环境的下,context 的生命周期里有两个阶段需要处理数据同步的问题。当某个 context 里的状态发生了变化并执行保存来更新 persistent store后,对于其他 context 来说有两个选择:1. persistent store 更新后,此时其他 context 与 persistent store 进行同步;2. persistent store 更新后,其他 context 并不立即同步,而在自身进行保存时与 persistent store 进行同步,两者有差异时需要解决冲突问题。前者采取的是「一处修改处处同步」的策略,所有的 context 中同步为一个版本,后者采取的是「多版本共存协商处理」的策略。以下讨论都基于多个 context,假设应用配置了两个 managed object context,mainContext,在主线程运行 ,另外一个 backgroundContext 用于后台处理。

多 context 单数据版本

在 context 中执行保存时,应用并不会主动告知其他 context。那么如何在多个 context 间进行通信呢?Core Data 提供了通知机制,context 执行保存时,会发出以下通知:
1.NSManagedObjectContextWillSaveNotification
Managed object context 即将执行保存操作时发出此通知,没有附带任何信息。
2.NSManagedObjectContextDidSaveNotification
Managed object context 的保存操作完成之后由该 context 自动发出此通知,包含了所有的新增、更新和删除的对象的信息。注意在通知里的 managed objects 只能在 context 所在的线程里使用。由于 context 也只能在自身的线程里执行操作,所以没法直接使用通知里的 managed objects,这时候应该通过 managed object 的 objectID 以及 context 的objectWithID(_ objectID: ) 来获取。

合并其他 context 的数据可以通过mergeChangesFromContextDidSaveNotification(_ notification:)来完成。在这个方法里,context 会更新发出通知的 context 里变化的任何同样的对象,引入新增的对象并处于 faults 状态,并删除在发出通知的 context 里已经删除的对象。

代码示例,在 backgroundContext 中编辑后使 mainContext 与之同步:

NSNotificationCenter.defaultCenter().addObserver(self,
selector: "backgroundContextDidSave:",
name: NSManagedObjectContextDidSaveNotification,
object: backgroundContext) func backgroundContextDidSave(notification: NSNotification){
mainContext.performBlock(){
mainContext.mergeChangesFromContextDidSaveNotification(notification)
}
}
多 context 多数据版本

在这种方案下,backgroundContext 并不针对 mainContext 的保存做出反应。在 mainContext 和 backgroundContext 中 fetch 了同类的 managed objects,两个 context 都发生了变化并且变化不一样,此时让 backgroundContext 与 mainContext 前后脚分别执行保存的话,就会发生冲突导致后者保存失败。

在 managed object context 中执行 fetch 操作时,会对 persistent store 里的状态进行快照,当 context 执行保存时,会使用快照与 persistent store 进行对比,如果状态不一致,说明 persistent store 在其他地方被更改了,而这个变化并不是当前 context 造成的,这样就造成了当前 context 状态的不连续,此时保存就会产生冲突。这里需要介绍 managed object context 的属性mergePolicy,这个属性指定了 context 的合并策略,决定了保存时合并数据发生冲突时如何应对,该属性有以下几种值:
1.NSErrorMergePolicy
默认策略,有冲突时保存失败,persistent store 和 context 都维持原样,并返回错误信息,是唯一反馈错误信息的合并策略。
2.NSMergeByPropertyStoreTrumpMergePolicy
当 persistent store 和 context 里的版本有冲突,persistent store 里的版本有优先权, context 里使用 persistent store 里的版本替换。
3.NSMergeByPropertyObjectTrumpMergePolicy
与上面相反,context 里的版本有优先权,persistent store 里使用 context 里的版本替换。
4.NSOverwriteMergePolicy
用 context 里的版本强制覆盖 persistent store 里的版本。
5.NSRollbackMergePolicy
放弃 context 中的所有变化并使用 persistent store 中的版本进行替换。

除了默认的 NSErrorMergePolicy在发生冲突时返回错误等待下一步处理外,其他的合并策略直接根据自身的规则来处理合并冲突,因此在选择时要谨慎处理。从上面的解释来看,似乎NSMergeByPropertyStoreTrumpMergePolicyNSRollbackMergePolicy没什么区别,NSMergeByPropertyObjectTrumpMergePolicyNSOverwriteMergePolicy也没有什么区别。区别在于怎么对待被覆盖的一方中没有冲突的变化(解释见此处)NSMergeByPropertyStoreTrumpMergePolicyNSMergeByPropertyObjectTrumpMergePolicy采取的是局部替换,前者 context 中没有冲突的变化不会受到影响,后者 persistent store 中没有冲突的变化不受影响;NSOverwriteMergePolicyNSRollbackMergePolicy 采取的是全局替换,persistent store 和 context 中只有一方的状态得以保留。

回到本节开始的场景,mainContext 和 backgroundContext 中的版本不一致,会产生合并冲突,解决方案有以下两种选择:
1.不管 mainContext 中是否发生改变,与 backgroundContext 中状态同步;

//此时 mainContext 和 backgroundContext 都采用默认合并策略即可。
mainContext.performBlock(){
do{
try mainContext.save()
} catch {
/*
清空 mainContext,其中所有的 managed objects 消失。
如果引用了其中的 managed objects 的话,注意在 reset 前取消对这些对象的引用。
*/
mainContext.reset()
//重新 fetch
let fetchRequest = ...
let updatedFetchedResults = try mainContext.executeFetchRequest(fetchRequest)
}
}

又或者,mainContext 的合并策略采用NSMergeByPropertyStoreTrumpMergePolicyNSRollbackMergePolicy,这样就省去了 reset 操作。实际上,采用这种方案不如上一个策略来得方便。
2.不管其他 context 中发生什么变化,当前 context 进行保存时直接覆盖 persistent store 里的版本,这种方案下 context 的合并策略需要采用NSOverwriteMergePolicyTypeNSMergeByPropertyObjectTrumpMergePolicy,而且执行保存时不会返回错误,不需要后续的处理。

小结

同步多个 context 是个比较复杂的事情,需要根据具体的需要来设定 context 的合并策略以及选择同步的时机,不仅仅限于以上的两种策略,融合两种策略也可以,当然那样可能会大大增加复杂度,更容易导致 Bug。另外,还有一种使用 child context 的方法,就是将其他 context 作为 context 的 parentContext,这种方法没有研究,自己有兴趣可以试试。
1.同步问题第一原则:不要跨线程使用 managed object,而应该通过其对应的 objectID,在其他线程里的 context 里来获取对象。
2.NSManagedObjectContext的合并方法mergeChangesFromContextDidSaveNotification(_ notification:)可以替完全复制另一个 context 的状态;如果你不想完全复制,可以使用更精确的方法refreshAllObjects(),这是 iOS 9 中推出的新方法;或者手动处理,当然,不推荐这么做。
3.利用NSMergePolicy来处理同步相对而言危险一点,你得明确知道你在做什么。

最后一站:大量数据操作

从上面的内容可以得知,在多线程环境下同步数据基本上不需要我们手动去处理 managed objects 的同步,因此处理大量数据的同步,关注的重点更多在于内存占用和性能。写代码要记住以下几点:
1.涉及大量数据的操作尽量要放在后台线程里处理,防止阻塞主线程;对于多 context 的结构,可以参考这篇文章《Concurrent Core Data Stacks – Performance Shootout》,作者通过验证,证明了「设置一个 persistent store coordinator 和两个独立的 contexts 被证明了是在后台处理 Core Data 的好方法」。
2.能够保持 faults 状态的 managed objects 尽量不要触发 fire,降低内存占用,同时也能提升响应速度。
3.fetch 大量数据时注意技巧,可以通过利用 predicate 来筛选实际需要的数据,限制 fetch 的总数量,设定合适的批量获取数量来降低 IO 的频次,这些需要在实际环境中寻找平衡点。
4.尽量让 context 中的 entity 类别少一些,降低对同步的需求。
(从 iOS 8 开始,Core Data 在性能方面有了较大的提升,尽量合理利用。)
5.使用异步请求 Asynchronous Fetch,尽管可以将 fetch 大量数据的操作放在后台线程里,但是这样依然会阻塞那个线程,使用异步请求,则依然可以在后台线程里进行其他操作,并且还有方便的进度提示和取消功能。
6.使用批量更新 Batch Update,有效降低内存占用并大幅提升保存的速度。以往在NSManagedObjectContext中进行保存时,只能将其中包含的变化进行保存,而 Batch Update 则是直接对 persistent store 进行更新而不需要将 managed objects 读入内存,可以大幅降低内存占用而且更新速度提升不少。但需要注意的是,使用批量更新并不会提醒 context,需要我们对 context 手动进行更新,而且没有进行有效验证,也需要开发者来保证有效性。
7.使用批量删除 Batch Delete,与批量更新类似,直接对 persistent store 进行操作,效率非常高,也有着和批量更新类似的问题。
8.使用 iOS 9 新增的NSManagedObjectContext的新 API:refreshAllObjects(),该方法会对 context 中注册的所有对象进行刷新,还没有保存的变化也会得到保留,这样就可以解放 6 和 7 中的手动更新工作。

文/seedante(简书作者)
原文链接:http://www.jianshu.com/p/37ab8f336f76
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

多线程操作Coredata(转)的更多相关文章

  1. C#中级-常用多线程操作(持续更新)

    一.前言       多线程操作一直是编程的常用操作,掌握好基本的操作可以让程序运行的更加有效.本文不求大而全,只是将我自己工作中常常用到的多线程操作做个分类和总结.平时记性不好的时候还能看看.本文参 ...

  2. java多线程操作

    进程是程序的一次动态的执行过程,它经历了从代码加载.执行完毕的一个完整过程,这个过程也是进程本身从产生.发展到最终消亡的过程. 多线程是实现并发机制的一种有效的手段.进程和线程一样,都是实现并发的一个 ...

  3. MFC的多线程操作

    记得用MFC做了一个图像自动修复软件,当时没有多线程操作这一概念,由于图像修复算法比较复杂,因此,当执行图像修复时,程序就像卡死了似得而不能做其他操作.其实MFC对这种情况有一种很好地解决方案,那就是 ...

  4. 更高效地提高redis client多线程操作的并发吞吐设计

    Redis是一个非常高效的基于内存的NOSQL数据库,它提供非常高效的数据读写效能.在实际应用中往往是带宽和CLIENT库读写损耗过高导致无法更好地发挥出Redis更出色的能力.下面结合一些redis ...

  5. C#多线程操作界面控件的解决方案(转)

    C#中利用委托实现多线程跨线程操作 - 张小鱼 2010-10-22 08:38 在使用VS2005的时候,如果你从非创建这个控件的线程中访问这个控件或者操作这个控件的话就会抛出这个异常.这是微软为了 ...

  6. c# BackGroundWorker 多线程操作的小例子

    在我们的程序中,经常会有一些耗时较长的运算,为了保证用户体验,不引起界面不响应,我们一般会采用多线程操作,让耗时操作在后台完成,完成后再进行处理或给出提示,在运行中,也会时时去刷新界面上的进度条等显示 ...

  7. swift语言之多线程操作和操作队列(下)———坚持51天吃掉大象(写技术文章)

    欢迎有兴趣的朋友,参与我的美女同事发起的活动<51天吃掉大象>,该美女真的很疯狂,希望和大家一起坚持51天做一件事情,我加入这个队伍,希望坚持51天每天写一篇技术文章.关注她的微信公众号: ...

  8. C# winform编程中多线程操作控件方法

    private void Form1_Load(object sender, EventArgs e) { Thread newthread = new Thread(new ThreadStart( ...

  9. sqlite:多线程操作数据库“database is locked”解决方法(二)

    上一篇博客<sqlite:多线程操作数据库“database is locked”解决方法>通过注册延时函数的方法来处理数据库被锁的问题.此方法固然能解决问题,但是在多个线程向数据库写入大 ...

随机推荐

  1. Java API ——StringBuffer类

    1.StringBuffer类概述 1)我们如果对字符串进行拼接操作,每次拼接,都会构建一个新的String对象,既耗时,又浪费空间.而StringBuffer就可以解决这个问题 2)线程安全的可变字 ...

  2. windows下使用远程工具登录虚拟机上的Linux、访问虚拟机上的服务 、端口转发、win7 telnet登陆虚拟机

    首先要清楚virtual box如何设置端口转发: 一篇文章: 如何使用VirtualBox进行端口转发 由于默认的方式是用NAT来做虚拟机网络的,因此如果从外网想访问虚拟机的应用会比较麻烦.以前一直 ...

  3. 类似百度文库pdf2swf+flexpaper解决pdf在线阅读的效果

    1:工具准备swftools.exe 下载http://www.swftools.org/download.html 安装至D盘SWFTools提供了一系列将各种文件转成swf的工具:font2swf ...

  4. API HOOK技术

    API HOOK技术是一种用于改变API执行结果的技术,Microsoft 自身也在Windows操作系统里面使用了这个技术,如Windows兼容模式等. API HOOK 技术并不是计算机病毒专有技 ...

  5. JavaScript Function.apply() 函数详解

    apply()函数用于调用当前函数functionObject,并可同时使用指定对象thisObj作为本次函数执行时函数内部的this指针引用. 该函数属于Function对象,所有主流浏览器均支持该 ...

  6. 【TopCoder】SRM 680 DIV 2

    1. BearPair之bigDistance1.1 题目概述在 <= 50的字符串中找位置i,j 满足(1) s[i] != s[j];(2) abs(i-j)尽可能大.若不存在返回-1, 否 ...

  7. bzoj3237

    首先我们可以把没有询问过的边处理掉,重构图 当然这样也不影响复杂度 考虑到每次询问要删除的边很少,我们完全可以整体处理 把询问划分成两个集合,在前半部分询问未出现边我们可以整体处理掉,缩点重编号(询问 ...

  8. OK335xS LAN8710 phy driver hacking

    /******************************************************************** * OK335xS LAN8710 phy driver h ...

  9. ArcGIS for Android示例解析之离线地图-----LocalTiledLayer

    转自:http://blog.csdn.net/wozaifeiyang0/article/details/7327423 LocalTiledLayer 看到这个标题是否是很激动,如题,该示例就是添 ...

  10. 单点登录系统(SSO)的开发思路

    单点登录并不是一个新鲜的玩意儿,比较官方的解释是企业业务整合的解决方案之一,通俗来讲SSO就是一个通用的用户中心,国内比较流行的UCenter就是一套单点登录解决方案.而近期以CSDN明文存储用户密码 ...