coreData 深入理解4 --總結 (執行緒安全與同步--iOS5 前後對比)
Core Data是iOS中很重要的一個部分,可以理解為基於SQLite(當然也可以是其他的Storage,如In-memory,只是SQLite比較常見)的一個ORM實現,所以有關係資料庫的特性,又不用寫SQL。順便吐一下槽,官方說法是使用Core Data能減少50%-70%的程式碼量,但相信用過的人應該都心裡明白,Core Data使用起來還是比較麻煩的,這也是為什麼有不少的第三方類庫來代替/二次包裝Core Data。
稍微複雜的應用就有可能出現同時處理多份資料的情況,這就需要用到多執行緒Core Data。在 iOS 5之前,官方推薦的是使用「Thread Confinement」,就是每個執行緒使用獨立的MOC(managed object context),然後共享一個PSC(persistent store coordinator)。同時線上程之間傳遞資料時,要傳遞objectID,而不是object,因為前者是執行緒安全的,後者不是。
如果A執行緒裡,對PSC執行了CUD(create, update, delete)操作,其他執行緒如何感知呢?這就需要通過監聽事件來實現。比如線上程A中監聽「NSManagedObjectContextDidSaveNotification」事件,如果執行緒B中執行了CUD操作,執行緒A就能感知到,並觸發響應的action,雖然可以通過noti userinfo來獲取managed objects,但因為它們是關聯到另一個MOC,所以無法直接操作它們,解決方法就是呼叫「mergeChangesFromContextDidSaveNotification:」方法。
用一張圖來形容的話,大體就是這樣:
- (void)_setupCoreDataStack
{
// setup managed object model
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Database" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// setup persistent store coordinator
NSURL *storeURL = [NSURL fileURLWithPath:[[NSString cachesPath] stringByAppendingPathComponent:@"Database.db"]];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_managedObjectModel];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
// handle error
}
// create MOC
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
// subscribe to change notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_mocDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:nil];
}
再來看看Notification Handler,主要作用就是合併新的變化。
- (void)_mocDidSaveNotification:(NSNotification *)notification
{
NSManagedObjectContext *savedContext = [notification object];
// ignore change notifications for the main MOC
if (_managedObjectContext == savedContext) {
return;
}
dispatch_sync(dispatch_get_main_queue(), ^{
[_managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
});
}
這種方式實現起來和維護起來都有點麻煩,所以iOS 5中就出現了更加方便和靈活的實現,也就是「Nested MOC」。
[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
可以看到在初始化時可以選擇ConcurrencyType,可選的有3個:
NSConfinementConcurrencyType
這個是預設項,每個執行緒一個獨立的Context,主要是為了相容之前的設計。
NSPrivateQueueConcurrencyType
建立一個private queue(使用GCD),這樣就不會阻塞主執行緒。
NSMainQueueConcurrencyType
建立一個main queue,使用主執行緒,會阻塞。
還有一個重要的變化是MOC可以指定parent。有了parent後,CUD操作會冒泡到parent。一個parent可以有多個child。parent還可以有parent。
因為UI相關的資料必須在主執行緒獲取,同時又要避免資料庫的I/O操作阻塞主執行緒,所以就有了下面這個模型:
我對這種實現方式的一個困惑是:child無法得知parent的變化,也就是說,如果NSFetchedResultsController綁定了Main MOC,當Background Write MOC save時,NSFetchedResultsController為何能知曉?求指點。
這種方式比「Thread Confinement」稍微簡單了點,也更明瞭。不過個人還是推薦使用MagicalRecord,因為實現起來更加簡單,等有空再寫一篇。
2013/06/17更新
之前的困惑已消除,NSFetchedResultsController
跟PSC無關,只要繫結的MOC有了save
動作,NSFetchedResultsController
就會收到通知,無論這個save
操作有沒有寫入到持久層。