在之前的介绍中,我们操作core data都是在主线程的,但是有的时候,我们对core data的操作可能会消耗很长的时间,比如类似微博,在程序启动的时候会加载之前存储在数据库中的数据,如果都在主线程操作的话,那么将会照成主线程堵塞,给用户不好的体验,这是我们就需要使用Core Data的多线程特性!
多线程解决方案
core data不是线程安全的,所以我们不能跨线程去操作它,如果涉及多线程的操作,最好的方式是每个线程都是不同的NSManagerObjectContext,对于线程间需要传递ManagedObject的,传递ManagedObject ID,通过objectWithID或者existingObjectWithID 来获取, 对于持久化存储协调器(NSPersistentStoreCoordinator)来说,可以多个线程共享一个NSPersistentStoreCoordinator。
使用Notification
Notification消息类型
- NSManagedObjectContextObjectsDidChangeNotification:当manager object对象中的属性值发生变化的时候会触发这个通知(当manager object以fetch结果接入到context的时候并不会触发该通知),通知的对象是对应的manager context,主要由以下几个值:NSInsertedObjectsKey, NSUpdatedObjectsKey, and NSDeletedObjectsKey。
- NSManagedObjectContextDidSaveNotification:当managed object context完成保存操作时触发该通知。
NSManagedObjectContextWillSaveNotification:当managed object context即将要执行保存操作时触发该通知。
使用
一种比较好的iOS模式就是使用一个NSPersistentStoreCoordinator,以及两个独立的Contexts,一个context负责主线程与UI协作,一个context在后台负责耗时的处理。这样做的好处就是两个context共享一个持久化存储缓存,而且这么做互斥锁只需要在sqlite级别即可。设置当主线程只读的时候,都不需要锁。
首先再主线程增加监听事件:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "didSaveNotification:", name: NSManagedObjectContextObjectsDidChangeNotification, object: nil)
func didSaveNotification(notification: NSNotification) {
if let currentContext = notification.object {
let context = currentContext as! NSManagedObjectContext
if context == appDelegate().managedObjectContext {
return
}
if context.persistentStoreCoordinator != appDelegate().persistentStoreCoordinator {
return
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.appDelegate().managedObjectContext.mergeChangesFromContextDidSaveNotification(notification)
})
}
}
然后我们把对core data的操作放入线程中进行操作,再线程中创建context的时候,指定persistentStoreCoordinator和主线程的一致:
func addDataUseObjc() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
let context = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
context.persistentStoreCoordinator = self.appDelegate().persistentStoreCoordinator
let author = NSEntityDescription.insertNewObjectForEntityForName("Author", inManagedObjectContext: context) as! Author
author.name = "jamy001"
author.age = NSNumber(integer: 25)
let book = NSEntityDescription.insertNewObjectForEntityForName("Book", inManagedObjectContext: context) as! Book
book.bookName = "很好一本书"
author.book = book
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
}
}
使用child/parent context
ChildContext和ParentContext是相互独立的。只有当ChildContext中调用Save了以后,才会把这段时间来Context的变化提交到ParentContext中,ChildContext并不会直接提交到NSPersistentStoreCoordinator中, parentContext就相当于它的NSPersistentStoreCoordinator。
child/parent context的结构图如下:
通常主线程context使用NSMainQueueConcurrencyType,其他线程childContext使用NSPrivateQueueConcurrencyType. child和parent的特点是要用Block进行操作,performBlock,或者performBlockAndWait,保证线程安全。这两个函数的区别是performBlock不会阻塞运行的线程,相当于异步操作,performBlockAndWait会阻塞运行线程,相当于同步操作,还有一点需要注意:childContext的type尽量不能使用NSConfinementConcurrencyType,因为使用这种的parent必须要是persistent store coordinator,不能为parent context!
func addDataUseChild() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
let context = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
context.parentContext = self.appDelegate().managedObjectContext
let author = NSEntityDescription.insertNewObjectForEntityForName("Author", inManagedObjectContext: context) as! Author
author.name = "jamy004"
author.age = NSNumber(integer: 26)
let book = NSEntityDescription.insertNewObjectForEntityForName("Book", inManagedObjectContext: context) as! Book
book.bookName = "很好一本书12"
author.book = book
context.performBlockAndWait({ () -> Void in
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
})
self.fetchData()
}
}