CoreData多上下文的設(shè)計(jì)

將CoreData和NSFetchedResultsController結(jié)合使用,可以簡(jiǎn)化任意類型表視圖的處理

有兩個(gè)場(chǎng)景茶宵,你可能想要使用多個(gè)上下文來(lái)處理:
1倍踪、簡(jiǎn)化添加梯捕、編輯新的條目
2匙瘪、避免阻塞UI
在這篇文章中铆铆,我想回顧一下設(shè)置上下文的方法,以便為您提供所需的內(nèi)容丹喻。

首先薄货,讓我們回顧下Context的設(shè)計(jì)。我們需要一個(gè)持久化協(xié)調(diào)器(persistentStoreCoordinator)來(lái)管理與數(shù)據(jù)庫(kù)文件的通訊碍论。因此你需要一個(gè)model谅猾,讓PSC知道數(shù)據(jù)庫(kù)的結(jié)構(gòu)。這個(gè)model將合并工程中定義的所有model鳍悠,讓CoreData知道關(guān)于數(shù)據(jù)庫(kù)的結(jié)構(gòu)信息税娜。PSC設(shè)置在MOC的一個(gè)屬性上。記住第一條規(guī)則:設(shè)置過(guò)PSC屬性的MOC調(diào)用saveContext的時(shí)候會(huì)將數(shù)據(jù)寫到磁盤贼涩。

image.png

觀察上圖。無(wú)論何時(shí)你在這個(gè)單一的MOC上插入薯蝎、更新或者刪除一個(gè)實(shí)體遥倦,fetchedResultsController都會(huì)收到一個(gè)改變和更新表視圖內(nèi)容的通知。這與上下文的保存無(wú)關(guān)占锯。您可以根據(jù)需要盡可能少地保存袒哥。Apple的模板在每次添加實(shí)體時(shí)保存,并且在applicationWillTerminate中保存消略。

這種方法適用于大部分情況堡称,但正如我上面提到的,它有兩個(gè)問(wèn)題艺演。您可能希望重用相同的視圖控制器來(lái)添加和編輯實(shí)體却紧。因此,您可能希望在呈現(xiàn)VC之前創(chuàng)建一個(gè)新實(shí)體胎撤,以便填充晓殊。這將導(dǎo)致更新通知觸發(fā)對(duì)fetchedResultsController的更新,即在呈現(xiàn)用于添加或編輯的模態(tài)視圖控制器之前伤提,會(huì)短暫的出現(xiàn)空行巫俺。

如果在saveContext之前產(chǎn)生的更新太大,而且保存時(shí)間超過(guò)1/60秒肿男,那么第二個(gè)問(wèn)題會(huì)很明顯介汹。因?yàn)檫@種情況下却嗡,用戶界面將被阻塞,直到保存完成嘹承,并且在滾動(dòng)時(shí)有明顯的跳轉(zhuǎn)窗价。

這兩個(gè)問(wèn)題都可以通過(guò)使用多上下文來(lái)解決。

傳統(tǒng)的多上下文方式

將每個(gè)上下文視為一個(gè)臨時(shí)的暫存器赶撰。iOS5之前舌镶,你能監(jiān)聽(tīng)其他上下文的改變,并且通過(guò)通知在主線上下文合并其他上下文的變更豪娜。一個(gè)典型的設(shè)計(jì)如下圖:

image.png

你將創(chuàng)建一個(gè)臨時(shí)上下文餐胀,在后臺(tái)隊(duì)列使用。為臨時(shí)上下文設(shè)置和主線程上下文一樣的PSC瘤载,臨時(shí)下文也可以保存變更到持久化文件否灾。Marcus Zarra如是說(shuō):

盡管NSPersistentStoreCoordinator也不是線程安全的,但是NSPersistentStoreCoordinator知道在使用的時(shí)候如何正確鎖定它鸣奔。因此墨技,我們可以根據(jù)需要將多個(gè)MOC加到單個(gè)NSPersistentStoreCoordinator而不用擔(dān)心發(fā)生沖突。

在后臺(tái)上下文調(diào)用saveContext挎狸,會(huì)將改變保存到持久化文件扣汪,同時(shí)會(huì)觸發(fā)NSManagedObjectContextDidSaveNotification通知。

在代碼中锨匆,類似這樣:

dispatch_async(_backgroundQueue, ^{
   // create context for background
   NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
   tmpContext.persistentStoreCoordinator = _persistentStoreCoordinator;
 
   // something that takes long
 
   NSError *error;
   if (![tmpContext save:&error])
   {
      // handle error
   }
});

創(chuàng)建一個(gè)臨時(shí)的上下文是非痴副穑快的,你不必?fù)?dān)心頻繁的創(chuàng)建和刪除那些臨時(shí)下文恐锣。關(guān)鍵點(diǎn)是設(shè)置臨時(shí)上下文的PSC與主線程上下文的PSC相同茅主,以便寫入也可以在后臺(tái)進(jìn)行。

我更喜歡簡(jiǎn)單的設(shè)置CoreData stack:

- (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];
}

現(xiàn)在請(qǐng)思考收到did save通知時(shí)土榴,如何處理通知:

- (void)_mocDidSaveNotification:(NSNotification *)notification
{
   NSManagedObjectContext *savedContext = [notification object];
 
   // ignore change notifications for the main MOC
   if (_managedObjectContext == savedContext)
   {
      return;
   }
 
   if (_managedObjectContext.persistentStoreCoordinator != savedContext.persistentStoreCoordinator)
   {
      // that's another database
      return;
   }
 
   dispatch_sync(dispatch_get_main_queue(), ^{
      [_managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
   });
}

第一個(gè)if诀姚,忽略自身的改變。假如我們的APP有多個(gè)CoreData DB玷禽,我們要阻止合并其他DB的改變赫段。為什么我要檢查PSC,因?yàn)樵谖业腶pp中碰到過(guò)這樣的問(wèn)題矢赁。最后瑞佩,我們通過(guò)系統(tǒng)提供的mergeChangesFromContextDidSaveNotification方法,合并改變坯台。通知有一個(gè)包含所有更改的字典炬丸,并且這個(gè)方法知道如何將它們插入MOC。

上下文之間傳遞托管對(duì)象

禁止在上下文中傳遞托管對(duì)象。有一個(gè)簡(jiǎn)單的方法檢索托管對(duì)象稠炬,使用它的ObjectID焕阿。ObjectID是線程安全的,你總是能在NSManagedObject的實(shí)例獲取到首启,然后在另一個(gè)上下文通過(guò)ObjectID獲取托管對(duì)象的副本暮屡。

NSManagedObjectID *userID = user.objectID;
 
// make a temporary MOC
dispatch_async(_backgroundQueue, ^{
   // create context for background
   NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
   tmpContext.persistentStoreCoordinator = _persistentStoreCoordinator;
 
   // user for background
   TwitterUser *localUser = [tmpContext objectWithID:userID];
 
   // background work
});

CoreData在iOS3第一個(gè)版本被引入后,你可以一直使用上面的方法毅桃。如果你的應(yīng)用程序最低支持iOS5褒纲,那么可以使用一種更加先進(jìn)的方法。

Parent/Child Contexts

iOS5可以讓MOC擁有一個(gè)parentContext钥飞。當(dāng)childContext調(diào)用saveContext的時(shí)候莺掠,parentContext不需要通過(guò)通知合并子上下文的變更。同時(shí)蘋果為MOC增加了在專有隊(duì)列進(jìn)行同步或異步執(zhí)行改變的功能读宙。

隊(duì)列的并發(fā)類型在NSManagedObjectContext初始化的時(shí)候通過(guò)initWithConcurrencyType指定彻秆。注意下圖,我添加了多個(gè)子上下文结闸,他們擁有同一個(gè)主隊(duì)列父上文唇兑。

image.png

當(dāng)子上下文保存時(shí),父上文就會(huì)獲取這些變化桦锄,fetchResultController也會(huì)得到這些變化扎附。由于后臺(tái)子上下文不知道PSC,所以這些變化不會(huì)保存到持久化文件结耀。要保存這些變化到持久化文件留夜,你需要在主線程父上下文調(diào)用saveContext。

首先需要改變主MOC的并發(fā)類型為NSMainQueueConcurrencyType饼记。上面提到的_setupCoreDataStack中MOC的初始化行改變?nèi)缦孪惆椋⑶也辉傩枰喜⑼ㄖ?/p>

_managedObjectContext =  [ [ NSManagedObjectContext alloc ] initWithConcurrencyType : NSMainQueueConcurrencyType ] ;
[ _managedObjectContext setPersistentStoreCoordinator : _persistentStoreCoordinator ] ;

后臺(tái)操作如下:

NSMangedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = mainMOC;
 
[temporaryContext performBlock:^{
   // do something that takes some time asynchronously using the temp context
 
   // push to parent
   NSError *error;
   if (![temporaryContext save:&error])
   {
      // handle error
   }
 
   // save parent to disk asynchronously
   [mainMOC performBlock:^{
      NSError *error;
      if (![mainMOC save:&error])
      {
         // handle error
      }
   }];
}];

現(xiàn)在慰枕,每個(gè)MOC都需要與performBlock: (async)或performBlockAndWait: (sync)一起使用具则。確保block中的操作在正確的線程執(zhí)行。在上面的例子中具帮,操作在后臺(tái)隊(duì)列執(zhí)行博肋。操作完成后,臨時(shí)上下文通過(guò)save方法將改變推送到父上下文蜂厅,然后mainMOC也有一個(gè)performBlock異步保存匪凡。再次使用performBlock,保證操作在正確的隊(duì)列上執(zhí)行掘猿。

子上下文不會(huì)自動(dòng)獲取父上下文的更新病游。你可以重新加載它們以獲得更新,大部分情況下它們是臨時(shí)的,因此我們不需要更新它們衬衬。只要主隊(duì)列的MOC獲得更新买猖,fetchResultController也獲得更新,我們就可以保存主MOC滋尉。

這種方式帶來(lái)的簡(jiǎn)化是玉控,你可以在點(diǎn)擊取消和保存按鈕的時(shí)候,為任意視圖控制器創(chuàng)建一個(gè)臨時(shí)上下文(作為子上下文)狮惜。編輯的時(shí)候高诺,可通過(guò)objectID將托管對(duì)象傳遞到臨時(shí)上下文。用戶可以更新托管對(duì)象的所有屬性碾篡,如果他點(diǎn)擊Save虱而,則保存臨時(shí)上下文。如果他點(diǎn)擊取消耽梅,你不必做任何事情薛窥,因?yàn)樽兏鼘⒑团R時(shí)上下文一起丟棄。

你暈了嗎眼姐?如果沒(méi)有诅迷,這就多上下文設(shè)計(jì)的全部。

異步保存

CoreData專家Marcus Zarra向我們展示了以下方法众旗,該方法基于上面的父/子方法新增了一個(gè)專門用于寫入磁盤的私有上下文罢杉。如前所述,長(zhǎng)時(shí)間的寫操作可能阻塞主線程UI贡歧。這種聰明的做法將寫操作放在私有隊(duì)列執(zhí)行滩租,使UI保持平滑。

image.png

CoreData的設(shè)置也非常簡(jiǎn)單利朵,我們只需要將persistentStoreCoordinator移動(dòng)到我們專有的用來(lái)執(zhí)行寫操作的MOC律想,并設(shè)置主MOC為他的child。

// create writer MOC
_privateWriterContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_privateWriterContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
 
// create main thread MOC
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.parentContext = _privateWriterContext;

現(xiàn)在绍弟,每次更新技即,我們需要執(zhí)行3次save操作:臨時(shí)上下文,主上下文(UI上下文)樟遣、私有寫上下文而叼。但是通過(guò)嵌套performBlocks方法,實(shí)現(xiàn)也是非常簡(jiǎn)單的豹悬。在長(zhǎng)時(shí)間的數(shù)據(jù)庫(kù)操作期間(導(dǎo)入大量數(shù)據(jù))以及寫入磁盤的時(shí)候葵陵,用戶界面保持暢通無(wú)阻。

結(jié)論

iOS極大地簡(jiǎn)化了后臺(tái)隊(duì)列處理CoreData和父上下文獲取子上下文的變更瞻佛。如果你的項(xiàng)目仍然需要支持iOS3/4脱篙,那這些依然無(wú)法滿足你的需求。如果你正在開(kāi)始一個(gè)最低支持iOS5及以上的項(xiàng)目,你可以立即使用Marcus Zarra的方法設(shè)計(jì)它绊困,如上所述忍弛。

說(shuō)明

水平有限,按自己的理解寫的考抄,權(quán)當(dāng)學(xué)習(xí)筆記细疚,歡迎斧正,英文好的同學(xué)請(qǐng)查看原文

相關(guān)鏈接:

https://www.cocoanetics.com/2013/02/zarra-on-locking/
https://code.tutsplus.com/tutorials/core-data-from-scratch-concurrency--cms-22131
http://www.cimgf.com/2011/05/04/core-data-and-threads-without-the-headache/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末川梅,一起剝皮案震驚了整個(gè)濱河市疯兼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贫途,老刑警劉巖吧彪,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異丢早,居然都是意外死亡姨裸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門怨酝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)傀缩,“玉大人,你說(shuō)我怎么就攤上這事农猬∩募瑁” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵斤葱,是天一觀的道長(zhǎng)慷垮。 經(jīng)常有香客問(wèn)我,道長(zhǎng)揍堕,這世上最難降的妖魔是什么料身? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮衩茸,結(jié)果婚禮上芹血,老公的妹妹穿的比我還像新娘。我一直安慰自己递瑰,他們只是感情好祟牲,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布隙畜。 她就那樣靜靜地躺著抖部,像睡著了一般。 火紅的嫁衣襯著肌膚如雪议惰。 梳的紋絲不亂的頭發(fā)上慎颗,一...
    開(kāi)封第一講書(shū)人閱讀 49,850評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼俯萎。 笑死傲宜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的夫啊。 我是一名探鬼主播函卒,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼撇眯!你這毒婦竟也來(lái)了报嵌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤熊榛,失蹤者是張志新(化名)和其女友劉穎锚国,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體玄坦,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡血筑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了煎楣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片豺总。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖择懂,靈堂內(nèi)的尸體忽然破棺而出园欣,到底是詐尸還是另有隱情,我是刑警寧澤休蟹,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布沸枯,位于F島的核電站,受9級(jí)特大地震影響赂弓,放射性物質(zhì)發(fā)生泄漏绑榴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一盈魁、第九天 我趴在偏房一處隱蔽的房頂上張望翔怎。 院中可真熱鬧,春花似錦杨耙、人聲如沸赤套。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)容握。三九已至,卻和暖如春车柠,著一層夾襖步出監(jiān)牢的瞬間剔氏,已是汗流浹背塑猖。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谈跛,地道東北人羊苟。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像感憾,于是被迫代替她去往敵國(guó)和親蜡励。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容