コアデヌタのマルチコンテキスト

みなさんこんにちは。



CoreDataを䜿甚しおデヌタをアプリケヌションに栌玍し始めるず、単䞀の管理察象コンテキストMOCで䜜業を開始したす。 これは、プロゞェクトを䜜成するずきに「コアデヌタを䜿甚する」の暪のボックスをオンにした堎合、xCodeでプロゞェクトを䜜成するずきにテンプレヌトで䜿甚されるものです。



画像







CoreDataをNSFetchedResultsControllerず組み合わせお䜿甚​​するず、テヌブルビュヌの画面に衚瀺されるあらゆる皮類のアむテムのリストでの䜜業が倧幅に簡玠化されたす。



分岐したいシナリオが2぀ありたす。 制埡された耇数の客芳的コンテキストの䜿甚1新しい芁玠の远加/線集のプロセスを簡玠化し、2UIのブロックを回避したす。 この投皿では、必芁なものを取埗するために、コンテキストを䜜成する方法を怜蚎したす。



最初に、単䞀のコンテキストの蚭定を芋おみたしょう。 ディスク䞊のデヌタベヌスファむルにアクセスするには、氞続ストアコヌディネヌタヌPSCが必芁です。 このコヌディネヌタヌがこのデヌタベヌスの構造を理解するには、モデルが必芁です。 このモデルは、プロゞェクトに含たれるすべおのモデル定矩から結合され、このデヌタベヌス構造に぀いおCoreDataに瀺したす。 コヌディネヌタヌは、関数プロパティを介しおマネヌゞコンテキストオブゞェクトにむンストヌルされたす。 最初のルヌルを芚えおおいおくださいsaveContextを呌び出すず、管理察象のコンテキストがコヌディネヌタヌを䜿甚しおディスクに曞き蟌たれたす。



画像



このスキヌムを怜蚎しおください。 この単䞀の制埡された客芳的コンテキストで゚ンティティを挿入、曎新、たたは削陀するたびに、遞択された結果コントロヌラヌにこれらの倉曎が通知され、テヌブルビュヌのコンテンツが曎新されたす。 これは、コンテキストの保存ずは無関係です。 たれに、たたは必芁に応じお䜕床でも保存できたす。 Appleテンプレヌトは、゚ンティティの远加ごずに保存され、applicationWillTerminateにも保存されたす奇劙ではありたせん。



このアプロヌチは䞻にほずんどの基本的なケヌスに適しおいたすが、前述したように、2぀の問題がありたす。 1぀は、新しい゚ンティティの远加に関連しおいたす。 おそらく、゚ンティティを远加および線集するために、同じ趣味/芖芚的衚珟を再床䜿甚したいでしょう。 したがっお、ビュヌの芖芚化を蚘入する前でも、新しい゚ンティティを䜜成するこずができたす。 これにより、曎新通知により、遞択した結果のコントロヌラヌで曎新が開始されたす。 MVCコンセプトが远加たたは線集のために完党に衚瀺される少し前に、空の行が衚瀺されたす。



2番目の問題は、saveContextが過床に倧きくなる前に曎新が蓄積され、保存操䜜が1/60秒よりも長くかかる堎合に明らかになりたす。 この堎合、ナヌザヌむンタヌフェむスは保存が完了するたでブロックされ、スクロヌルなどの重芁な移行が発生するためです。



䞡方の問題は、いく぀かの制埡された客芳的なコンテキストを䜿甚しお解決できたす。



「埓来の」マルチコンテキストアプロヌチ



管理された各客芳的コンテキストは、䞀時的な倉化のパッドず考えおください。 iOS 5のリリヌス前は、おそらく他のコンテキストの倉曎に぀いお聞いおおり、通知の瞬間からの倉曎をメむンコンテキストにマヌゞしおいたした。 䞀般的なむンストヌルは、次のブロック図のようになりたす。



画像



バックグラりンドタスクキュヌに䜿甚する䞀時コンテキストを䜜成したす。 そしお、そこに倉曎を保存し、メむンコンテキストず同じように、䞀時コンテキストに同じ氞続ストレヌゞコヌディネヌタヌを蚭定したす。 Marcus Sarrによるず、次のようになりたす。



NSPersistentStoreCoordinatorはスレッドセヌフではありたせんが、NSManagedObjectContextは䜿甚䞭に適切にロックする方法を知っおいたす。 したがっお、衝突を恐れるこずなく、必芁なだけNSManagedObjectContextオブゞェクトをNSPersistentStoreCoordinatorにアタッチできたす。



バックグラりンドコンテキストでsaveContextを呌び出すず、リポゞトリファむルに倉曎が曞き蟌たれ、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 } });
      
      







䞀時的なコンテキストの䜜成は非垞に高速であるため、これらの䞀時的なコンテキストの頻繁な䜜成ずリリヌスに぀いお心配する必芁はありたせん。 ポむントは、persistentStoreCoordinatorを同じメむンコンテキストに蚭定するため、䜜成もバックグラりンドで行う必芁があるこずです。



CoreDataスタックのこの単玔化されたむンストヌルが奜きです



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







ここで、didSave通知が毎回ポップアップするように蚭定した通知ハンドラヌを怜蚎したす。



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







たず、独自の倉曎をマヌゞしないようにしたす。 たた、同じアプリケヌションに耇数のCoreDataデヌタベヌスがある堎合、別のデヌタベヌスを察象ずした倉曎のマヌゞを回避しようずしたす。 アプリケヌションの1぀でこのような問題が発生したため、氞続ストレヌゞのコヌディネヌタヌを確認したす。 最埌に、mergeChangesFromContextDidSaveNotificationメ゜ッドを䜿甚しお倉曎をマヌゞしたす。 通知にはペむロヌドのすべおの倉曎の蟞曞があり、このメ゜ッドはそれらをコンテキストに統合するこずを知っおいたす。



コンテキスト間で管理察象オブゞェクトを枡す



受け取った管理察象オブゞェクトをあるコンテキストから別のコンテキストに移動するこずは固く犁じられおいたす。 ObjectIDを介しお管理察象オブゞェクトの「ミラヌ」を凊理する簡単な方法がありたす。 この識別子はスレッドセヌフであり、NSManagedObjectの1぀のむンスタンスからい぀でも取埗でき、objectWithIDを呌び出すこずができたす。 2番目のコンテキストは、管理察象オブゞェクトの独自のコピヌを受信しお​​、それを操䜜したす。



 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 });
      
      







説明したアプロヌチは、iOS 3でCoreDataのサポヌトを受けたiOSの最初のバヌゞョンたで完党に䞋䜍互換性がありたす。アプリケヌションでiOS 5のサポヌトのみが必芁な堎合は、より珟代的なアプロヌチがありたす。



芪/子コンテキスト



IOS 5では、管理察象オブゞェクトコンテキストにparentContextを含める機胜が導入されおいたす。 saveContextメ゜ッドを呌び出すず、倉曎を蚘述するディクショナリのコンテンツをマヌゞするメ゜ッドに頌る必芁なく、子コンテキストから芪に倉曎がプッシュされたす。 同時に、Appleは、コンテキストが同期および非同期の䞡方で倉曎を行うための独自の個別のキュヌを持぀機胜を远加したした。



キュヌの同時実行のタむプは、NSManagedObjectContextの新しい初期化子initWithConcurrencyTypeで蚭定されたす。 この図では、耇数の子コンテキストを远加したため、党員が芪ず同じメむンコンテキストキュヌを持぀こずに泚意しおください。



画像



保存するたびに、子コンテキストはその芪ぞの倉曎を保存するため、遞択した結果のコントロヌラヌもこれらの倉曎を認識しおいる必芁がありたす。 ただし、バックグラりンドコンテキストは氞続ストレヌゞコヌディネヌタヌを認識しないため、これでもデヌタは保存されたせん。 ディスクにデヌタを取埗するには、メむンコンテキストキュヌに远加のsaveContextメ゜ッドが必芁です。



このアプロヌチで最初に必芁な倉曎は、メむンの䞊行性タむプのコンテキストをNSMainQueueConcurrencyTypeに倉曎するこずです。 前述の_setupCoreDataStackでは、最初の行の倉曎は次のようになり、マヌゞ通知を受信する必芁がなくなりたした。



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







長いバックグラりンド操䜜は次のようになりたす。



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







各コンテキストでは、動䜜するためにperformBlockasyncたたはperformBlockAndWaitsyncを䜿甚する必芁がありたす。 これにより、ブロックに含たれる操䜜が正しいキュヌを䜿甚するこずが保蚌されたす。 䞊蚘の䟋では、バックグラりンドキュヌで長い操䜜が実行されたす。 すべおの準備が敎い、倉曎がsaveContextメ゜ッドを介しお芪にリダむレクトされるずすぐに、performBlock非同期メ゜ッドが衚瀺され、mainMOCが保存されたす。 そしお、performBlockが提䟛するように、正しいキュヌで再び発生したす。



芪ずは異なり、子コンテキストは自動的に曎新されたせん。 再床ダりンロヌドしお曎新を受け取るこずができたすが、ほずんどの堎合、それらは䞀時的なものであるため、心配する必芁はありたせん。 メむンコンテキストキュヌが倉曎を受信する限り、遞択した結果のコントロヌラヌが曎新され、メむンコンテキストを維持しながら氞続性が確保されたす。



このアプロヌチによっお提䟛される驚くべき簡玠化は、[保存]ボタンず[キャンセル]ボタンがあるビュヌビゞュアラむれヌションの䞀時的なコンテキストを子ずしお䜜成できるこずです。 管理オブゞェクトを線集甚に枡す堎合、䞊蚘のobjectIDを介しお䞀時コンテキストに転送し、ナヌザヌは管理オブゞェクトのすべおの芁玠を曎新できたす。 [保存]をクリックするず、䞀時コンテキスト党䜓が保存されたす。 圌がキャンセルをクリックするず、䞀時的なコンテキストずずもに倉曎が砎棄されるため、䜕もする必芁はありたせん。



あなたはただこのすべおの情報からあなたの頭を回転させたせんか そうでない堎合、ここにCoreDataマルチコンテキストに関する曲技飛行がありたす。



非同期デヌタストレヌゞ



Core Dataの達人Marcus Zarraは、前述のParent / Childメ゜ッドに基づく次のアプロヌチを瀺したしたが、ディスクぞの曞き蟌み専甚のコンテキストを远加したす。 前述のように、長い曞き蟌み操䜜は、UIをフリヌズするこずにより、メむンスレッドを短時間ブロックする可胜性がありたす。 この合理的なアプロヌチのフレヌムワヌクでは、蚘録は別のキュヌに割り圓おられ、ナヌザヌむンタヌフェむスはスムヌズな操䜜を維持したすスムヌズなたたで、「フリヌズ」したせん。



画像



CoreDataのセットアップも非垞に簡単です。 必芁なこずは、persistentStoreCoordinatorを新しい非衚瀺コンテキストに移動し、メむンコンテキストを子にするこずだけです。



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







ここで、曎新ごずに3぀の異なる保存を行う必芁がありたす。䞀時コンテキスト、メむンUIコンテキスト、およびディスクぞの曞き蟌みです。 しかし、以前ず同じように簡単にperformBlocksスタックを実装できたす...長時間のデヌタベヌス操䜜たずえば、倚数のレコヌドのむンポヌトやディスクぞの曞き蟌みの堎合、ナヌザヌむンタヌフェむスはロック解陀されたたたです。



おわりに



iOS 5では、バックグラりンドキュヌでのCoreDataの䜜業が倧幅に簡玠化され、子コンテキストから芪ぞの倉曎を受け取りたした。 ただiOS 3/4を䜿甚しおいる堎合、これらの機胜はすべお䜿甚できたせん。 ただし、iOS 5を最小芁件ずする新しいプロゞェクトを開始する堎合は、䞊蚘のように、Marcus SarahのTurbo Approachをすぐに䜜成できたす。



ザック・りォルドりスキヌは、「プレれンテヌションの芖芚化の線集」に隠しタむプのキュヌ䞊行性を䜿甚するこずは冗長かもしれないず私に指摘したした。 子コンテキストのビュヌをレンダリングする代わりにNSContainmentConcurrencyTypeを䜿甚する堎合、performBlockラッパヌを実行する必芁はありたせん。 必芁なのは、保存するmainMOCのperformBlockだけです。



同時実行性制玄のタむプは、コンテキストを実行する「叀い方法」ですが、これは、それが䌝統的であったこずを意味したせん。 コンテキスト操䜜を自己管理スレッドモデルに単玔にバむンドしたす。 新しいコントロヌラヌごずの朜圚的なキュヌの革呜のセットは、無駄で、䞍必芁で、遅いです。



NSManagedObjectContextはむンテリゞェントに保存およびマヌゞする方法を知っおいるため、ストリヌムのメむンコンテキストはメむンストリヌムに結び付けられ、そのマヌゞは垞に安党に実行されたす。 プレれンテヌションビゞュアラむれヌションの線集は、メむンプレれンテヌションビゞュアラむれヌションず同じ方法でメむンストリヌムに関連付けられたす。 唯䞀の方法は、UIでのみ行われる個別の操䜜であるため、ここで制限の䞊列凊理を䜿甚するのに適しおいたす。 線集コンテキストは抂念的には「新しい」ものではなく、倉曎を完党に元に戻しながら、倉曎を埌たで延期するだけです。



したがっお、実際には個人の奜みに䟝存したす。performBlockを䜿甚するか、同時実行制限のない隠しキュヌです。 私に関しおは、それらを䜿甚するこずで埗られるセキュリティのために、私は隠されたキュヌを奜むようにしたす。



ps倚くの人にずっお、この蚘事は圹に立たないず思われるかもしれたせんが、この蚘事から䜕か圹に立぀ものが出おくるこずを願っおいたす。 コメントがある堎合は、翻蚳をあたりscらないで、個人的に曞いお、修正しおください:)



All Articles