私は最近、Grand Central Dispatch を使用してバックグラウンドでフェッチとインポートを管理するように Core Data ドリブン データベース コントローラーを書き直しました。コントローラーは 2 つの NSManagedContext で操作できます。
NSManagedObjectContext *mainMoc
メインスレッドのインスタンス変数。dipatch_get_main_queue()
このコンテキストは、メイン スレッドまたはグローバル キューによる UI へのクイック アクセスでのみ使用されます。NSManagedObjectContext *bgMoc
バックグラウンド タスク用 (テーブルの NSFetchedresultsController のデータのインポートとフェッチ)。このバックグラウンド タスクは、ユーザー定義のキューdispatch_queue_t bgQueue
(データベース コントローラー オブジェクトのインスタンス変数) によってのみ起動されます。
テーブルのデータのフェッチはバックグラウンドで行われ、より大きな、またはより複雑な述語が実行されたときにユーザー UI をブロックしません。
テーブル ビュー コントローラーでの NSFetchedResultsController のフェッチ コードの例:
-(void)fetchData{
dispatch_async([CDdb db].bgQueue, ^{
NSError *error = nil;
[[self.fetchedResultsController fetchRequest] setPredicate:self.predicate];
if (self.fetchedResultsController && ![self.fetchedResultsController performFetch:&error]) {
NSSLog(@"Unresolved error in fetchData %@", error);
}
if (!initial_fetch_attampted)initial_fetch_attampted = YES;
fetching = NO;
dispatch_async(dispatch_get_main_queue(), ^{
[self.table reloadData];
[self.table scrollRectToVisible:CGRectMake(0, 0, 100, 20) animated:YES];
});
});
} // fetchData 関数の終わり
bgMoc
mainMoc
を使用して保存時にマージしますNSManagedObjectContextDidSaveNotification
:
- (void)bgMocDidSave:(NSNotification *)saveNotification {
// CDdb - bgMoc didsave - merging changes with main mainMoc
dispatch_async(dispatch_get_main_queue(), ^{
[self.mainMoc mergeChangesFromContextDidSaveNotification:saveNotification];
// Extra notification for some other, potentially interested clients
[[NSNotificationCenter defaultCenter] postNotificationName:DATABASE_SAVED_WITH_CHANGES object:saveNotification];
});
}
- (void)mainMocDidSave:(NSNotification *)saveNotification {
// CDdb - main mainMoc didSave - merging changes with bgMoc
dispatch_async(self.bgQueue, ^{
[self.bgMoc mergeChangesFromContextDidSaveNotification:saveNotification];
});
}
NSfetchedResultsController デリゲートには、実装されているメソッドが 1 つだけあります (簡単にするため)。
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
dispatch_async(dispatch_get_main_queue(), ^{
[self fetchData];
});
}
このようにして、コア データに関する Apple の推奨事項に従っています。スレッドごとに 1 つの NSManagedObjectContext です。最後の 2 つの理由から、このパターンが完全にクリーンではないことはわかっています。
bgQueue
中断後に必ずしも同じスレッドを起動するとは限りませんが、シリアルであるため、それほど重要ではありません (2 つのスレッドがbgMoc
専用の NSManagedObjectContext にアクセスしようとすることは決してありません)。- テーブル ビューのデータ ソース メソッドは、NSFetchedResultsController に bgMoc からの情報 (フェッチは bgQueue で行われるため) を要求することがあります (セクション カウント、セクション カウント内のフェッチされたオブジェクトなど....
この欠陥がある場合でも、このアプローチはアプリケーション実行時間の 95% でかなりうまく機能します...
そして、ここに私の質問があります:
ときどき、非常にランダムにアプリケーションがフリーズしますが、クラッシュはしません。どのタッチにも応答せず、ライブに戻す唯一の方法は、完全に再起動することです (バックグラウンドに切り替えたり、バックグラウンドから切り替えたりしても役に立ちません)。例外はスローされず、コンソールには何も出力されません (Xcode ですべての例外に対してブレークポイントが設定されています)。
メインスレッドで何か難しいことが起こっているかどうかを確認するために、Instruments (特に時間プロファイル) を使用してデバッグしようとしましたが、何も表示されません。
ここで GCD と Core Data が主な容疑者であることは承知していますが、これを追跡/デバッグする方法がわかりません。
これは、すべてのタスクを非同期的にのみキューにディスパッチした場合にも発生することを指摘しておきます (どこでもdispatch_asyncを使用)。これは、単なる標準的なデッドロックではないと思います。
どうすれば何が起こっているのか、より多くの情報を得ることができる可能性やヒントはありますか? いくつかの追加のデバッグ フラグ、楽器の魔法のトリック、ビルド設定など...
NSFetchedResultsController のバックグラウンドフェッチとバックグラウンドインポートをより良い方法で実装する方法へのポインタと同様に、何が原因である可能性があるかについての提案は非常に高く評価されています。