質問:NSFetchedResultsControllerをトリガーしてUIを更新するように、子コンテキストに親コンテキストで永続化された変更を表示するにはどうすればよいですか?
これが設定です:
大量のXMLデータ(約200万レコード、それぞれおよそテキストの通常の段落のサイズ)をダウンロードして追加するアプリがあります。.sqliteファイルのサイズは約500 MBになります。このコンテンツをCore Dataに追加するには時間がかかりますが、データが段階的にデータストアに読み込まれている間、ユーザーがアプリを使用できるようにする必要があります。大量のデータが移動されているので、ユーザーには見えないように見えないため、ハングやジッターが発生せず、バターのようにスクロールします。それでも、アプリの方が便利で、データが追加されるので、データがCore Dataストアに追加されるのをいつまでも待つことはできません。コードでは、これはインポートコードで次のようなコードを避けたいことを意味します。
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
アプリはiOS 5のみなので、サポートする必要がある最も遅いデバイスはiPhone 3GSです。
現在のソリューションを開発するためにこれまでに使用したリソースは次のとおりです。
Appleのコアデータプログラミングガイド:データの効率的なインポート
- 自動解放プールを使用してメモリを維持する
- 関係コスト。フラットにインポートし、最後に関係をパッチアップする
- あなたがそれを助けることができるかどうか問い合わせないでください、それはO(n ^ 2)の方法で物事を遅くします
- バッチでインポート:保存、リセット、排出、繰り返し
- インポート時に元に戻すマネージャーをオフにする
- 3つのコンテキストを使用:マスター、メイン、制限コンテキストタイプ
iDeveloper TV-Mac、iPhone、iPadのコアデータの更新
- performBlockを使用して他のキューで保存を実行すると、処理が速くなります。
- 暗号化は物事を遅くし、可能であればそれをオフにします。
Marcus Zarraによるコアデータ内の大きなデータセットのインポートと表示
- 現在の実行ループに時間を与えることでインポートを遅くすることができるので、ユーザーはスムーズに作業できます。
- サンプルコードは、大規模なインポートを実行してUIの応答性を維持できることを証明していますが、3つのコンテキストや非同期のディスクへの保存ほど高速ではありません。
私の現在の解決策
NSManagedObjectContextのインスタンスが3つあります。
masterManagedObjectContext-これはNSPersistentStoreCoordinatorを持ち、ディスクへの保存を担当するコンテキストです。これを行うのは、保存が非同期になり、したがって非常に高速になるためです。起動時に次のように作成します。
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext-これは、UIがあらゆる場所で使用するコンテキストです。これは、masterManagedObjectContextの子です。私はそれを次のように作成します:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext-このコンテキストは、XMLデータをCore DataにインポートするNSOperationサブクラスで作成されます。オペレーションのメインメソッドで作成し、そこでマスターコンテキストにリンクします。
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
これは実際には非常に高速に動作します。この3つのコンテキスト設定を行うだけで、インポート速度を10倍以上向上させることができました。正直なところ、これは信じがたいことです。(この基本設計は、標準のコアデータテンプレートの一部である必要があります...)
インポートプロセス中に、2つの方法を保存します。バックグラウンドコンテキストで保存する1000アイテムごと:
BOOL saveSuccess = [backgroundContext save:&error];
次に、インポートプロセスの最後に、マスター/親コンテキストに保存します。これにより、表面的には、メインコンテキストを含む他の子コンテキストに変更がプッシュされます。
[masterManagedObjectContext performBlock:^{
NSError *parentContextError = nil;
BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
問題:問題は、ビューを再ロードするまでUIが更新されないことです。
NSFetchedResultsControllerを使用してデータが供給されているUITableViewを持つ単純なUIViewControllerがあります。インポートプロセスが完了すると、NSFetchedResultsControllerは親/マスターコンテキストからの変更がないので、見慣れたようにUIが自動的に更新されません。スタックからUIViewControllerをポップして再度ロードすると、すべてのデータがそこにあります。
質問:NSFetchedResultsControllerをトリガーしてUIを更新するように、子コンテキストに親コンテキストで永続化された変更を表示するにはどうすればよいですか?
私はアプリをハングさせるだけの以下を試しました:
- (void)saveMasterContext {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
NSError *error = nil;
BOOL saveSuccess = [masterManagedObjectContext save:&error];
[notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == mainManagedObjectContext) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES];
return;
}
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}