將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ù)寫到磁盤贼涩。
觀察上圖。無(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ì)如下圖:
你將創(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ì)列父上文唇兑。
當(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保持平滑。
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/