在之前的介紹中,我們操作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()
}
}