Core Data 多线程操作实战篇
最近在解决百度音乐iPhone客户端偶现数据库操作crash的问题,顺手整理了下CoreData的多线程原则,以及实际开发时应该如何遵守这些原则。
Core Data多线程操作的基本原则
- 不允许跨线程访问MOC: 在某一个MOC上的CRUD操作只能在它的操作线程上进行
- 不允许跨线程访问MO:对MO的操作只能在它所属的MOC的操作线程上进行。需要注意的是,访问一个FRC的fetchedObjects数组也只能在FRC所属的MOC的操作线程上进行
Core Data的新版本
所谓的新版本,其实是从iOS 5.0就开始支持了。新版本的Core Data废弃了confinement context,替代它的是queue context。这两种context的区别在于,使用confinement context必须自己保证CRUD操作只在创建它的线程上执行,也就是说创建这个confinement context的线程就是它的操作线程。 而使用queue context则只需要用-performBlock:
或者-performBlockAndWait:
方法执行CRUD操作,这些操作都被dispatch到context持有的serial _dispatchQueue
上执行。
Magical Record的新版本(coming soon)
根据Magical Record的说明,从3.0版本起,MagicalRecord中的-MR_contextForCurrentThread
将被弃用。刚开始我没想清楚其中的原因,后来通过在github上提问,Magical Record的维护者之一tonyarnold给出的原因是:
- Core Data已经废弃了confinement context
- Core Data中新的queue context内部使用serial dispatch_queue来保证线程安全
- MR中的
-MR_contextForCurrentThread
方法是把一个queue context存储在一个thread的字典里。这样会导致调用这个方法的线程一定不是queue context的操作线程.
我是这么理解的:
- 我们使用
-MR_contextForCurrentThread
这个方法通常的习惯是,拿到这个方法返回的context之后,直接在这个context上进行CRUD操作。这在旧版本使用confinement context的Core Data是没有问题的。因为这个confinement context的创建线程就是它的合法操作线程 - 对于queue context, 如果我们继续按照1中的方式使用这个context是有问题。原因就是tonyarnold给出的第三点。正确的做法应该是把CRUD操作都放在
-performBlock:
或-performBlockAndWait:
中 - 我认为,如果我们能用正确的方式使用
-MR_contextForCurrentThread
方法返回的context, 那么是没有问题的
App中应该如何遵守Core Data的多线程操作原则
如果你App也使用了Magical Record, 那么建议你按照官方说明上的方式替换掉它打算废弃的API。比如用每次创建一个新的-MR_context
的方式来替换掉-MR_contextForCurrentThread
的使用。
下面是我在修改百度音乐iPhone客户端的数据库操作时总结的几个方法:
-
每个入参有MO的方法
- 自己保证(不是由调用者保证)对MO的访问和修改在MO.managedObjectContext的操作线程上执行
- 此方法应该仅把DB操作放在MO.managedObjectContext的操作线程上。 这么要求是为了避免修改数据库操作之外的任务的线程安排
- 注意,不要把FRC.fetchedObjects作为参数传递。对这个数组做遍历、或者取大小等的操作必须要在FRC.managedObjectContext中进行,而仅由传入的FRC.fetchedObjects无法获取此信息
-
MO的category中的方法
- -方法,没有必要用
-performBlock:
或-performBlockAndWait:
包起来。因为调用者应该有这样的意识:对MO的操作需要在它所属的MOC上进行。这点和CoreData的要求是一致的 - +方法需要自己保证不违反线程操作原则。 这点和MagicalRecord保持一致
- -方法,没有必要用
Core Data有关的Debug方式
- -com.apple.CoreData.ConcurrencyDebug 3
- -com.apple.CoreData.SQLDebug 3
- -com.apple.CoreData.SyntaxColoredLogging YES