質問: 子コンテキストを取得して親コンテキストに永続化された変更を表示し、NSFetchedResultsController をトリガーして UI を更新するにはどうすればよいですか?
セットアップは次のとおりです。
大量の XML データ (約 200 万レコード、それぞれがテキストの通常の段落のサイズにほぼ等しい) をダウンロードして追加するアプリを入手しました。.sqlite ファイルのサイズは約 500 MB になります。このコンテンツを Core Data に追加するには時間がかかりますが、データがデータ ストアに段階的に読み込まれる間、ユーザーがアプリを使用できるようにする必要があります。大量のデータが移動されていることをユーザーに見えないようにし、知覚できないようにする必要があります。そのため、ハングやジッターはありません。バターのようにスクロールします。それでも、アプリはデータが追加されるほど便利になるため、コア データ ストアにデータが追加されるのを永遠に待つことはできません。コードでは、これは、インポート コードで次のようなコードを避けたいことを意味します。
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
アプリは iOS 5 のみであるため、サポートする必要がある最も遅いデバイスは iPhone 3GS です。
現在のソリューションを開発するためにこれまでに使用したリソースは次のとおりです。
Apple の Core Data Programming Guide: Efficiently Importing Data
- 自動解放プールを使用してメモリを低く抑える
- 関係コスト。フラットにインポートし、最後に関係を修正する
- あなたがそれを助けることができるかどうかを問い合わせないでください.O(n ^ 2)の方法で物事が遅くなります.
- バッチでインポート: 保存、リセット、排出、繰り返し
- インポート時に Undo Manager をオフにする
iDeveloper TV - コア データ パフォーマンス
- 3 つのコンテキストを使用: マスター、メイン、および監禁コンテキスト タイプ
iDeveloper TV - Mac、iPhone、iPad のコア データの更新
- performBlock を使用して他のキューで保存を実行すると、処理が速くなります。
- 暗号化は速度を低下させます。可能であればオフにしてください。
Marcus Zarra による Core Data の大きなデータ セットのインポートと表示
- 現在の実行ループに時間を与えることでインポートを遅くすることができるため、ユーザーは物事をスムーズに感じることができます。
- サンプル コードは、大規模なインポートを実行して 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 倍以上向上しました。正直なところ、これは信じがたいことです。(この基本設計は、標準の Core Data テンプレートの一部である必要があります...)
インポート プロセス中に、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];
}