1. 程式人生 > >coreData 深入理解4 --總結 (執行緒安全與同步--iOS5 前後對比)

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:」方法。

用一張圖來形容的話,大體就是這樣:

iOS multi thread Core Data before iOS 5

- (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操作阻塞主執行緒,所以就有了下面這個模型:

multi thread ios 5 nested MOC

我對這種實現方式的一個困惑是:child無法得知parent的變化,也就是說,如果NSFetchedResultsController綁定了Main MOC,當Background Write MOC save時,NSFetchedResultsController為何能知曉?求指點。

這種方式比「Thread Confinement」稍微簡單了點,也更明瞭。不過個人還是推薦使用MagicalRecord,因為實現起來更加簡單,等有空再寫一篇。

2013/06/17更新

之前的困惑已消除,NSFetchedResultsController跟PSC無關,只要繫結的MOC有了save動作,NSFetchedResultsController就會收到通知,無論這個save操作有沒有寫入到持久層。

參考