27

アプリケーション UI (メイン スレッド) への影響を最小限に抑えながら、かなり大きなコア データ ベースのデータセットをバックグラウンドで更新する最善の方法を探しています。

このトピックについては、次のような優れた資料があります。

私の調査と個人的な経験に基づいて、データベース (SQLite) レベルでのみデータを共有する 2 つの別個のコア データ スタックを効果的に使用することが、利用可能な最良のオプションです。これはNSPersistentStoreCoordinators、それぞれが独自の を持つ2 つの別個の が必要であることを意味しNSManagedObjectContextます。データベースで先行書き込みロギングを有効にすると (iOS 7 以降のデフォルト)、ほぼすべてのケースでロックの必要性を回避できます (ただし、2 つ以上の同時書き込みがある場合を除きますが、これは私のシナリオではありそうにありません)。

効率的なバックグラウンド更新を行い、メモリを節約するには、データをバッチで処理し、バックグラウンド コンテキストを定期的に保存する必要もあります。これにより、ダーティ オブジェクトがデータベースに保存され、メモリからフラッシュされます。この時点で生成されるを使用しNSManagedObjectContextDidSaveNotificationて、背景の変更をメイン コンテキストにマージできますが、通常、バッチが保存された直後に UI を更新することは望ましくありません。バックグラウンド ジョブが完全に完了するまで待ってから、UI を更新します (WWDC セッションと objc.io の両方の記事で推奨されています)。これは事実上、アプリケーションのメイン コンテキストが一定期間データベースと同期していないことを意味します。

つまり、変更をマージするようにメイン コンテキストにすぐに指示せずに、この方法でデータベースを変更した場合、何が問題になる可能性があるのでしょうか? 私はそれがバラのすべての太陽の光ではないと仮定しています.

頭の中にある特定のシナリオの 1 つは、メイン コンテキストにロードされたオブジェクトに対して障害を解決する必要がある場合、バックグラウンド操作によってそのオブジェクトがデータベースから削除された場合にどうなるかということです。これは、たとえば、batchSize を使用してオブジェクトを段階的にメモリにフェッチする NSFetchedResultsController ベースのテーブル ビューで発生する可能性がありますか? つまり、まだ完全にフェッチされていないオブジェクトは削除されますが、オブジェクトをロードする必要があるポイントまでスクロールします。これは潜在的な問題ですか?他のことがうまくいかないことはありますか?この件に関するご意見をいただければ幸いです。

4

4 に答える 4

5

素晴らしい質問です。

つまり、まだ完全にフェッチされていないオブジェクトは削除されますが、オブジェクトをロードする必要があるポイントまでスクロールします。これは潜在的な問題ですか?

残念ながら、それは問題を引き起こします。次の例外がスローされます。

Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0xc544570 <x-coredata://(...)>'

このブログ投稿(「コア データで同時実行を行う方法」というタイトルのセクション) は多少役立つかもしれませんが、このトピックを網羅しているわけではありません。私は現在取り組んでいるアプリで同じ問題に苦しんでおり、それについての記事を読みたい.

于 2013-10-10T13:44:23.037 に答える
3

あなたの質問、コメント、および私自身の経験に基づいて、あなたが解決しようとしているより大きな問題は次のとおりです。またはコンテキスト内の管理対象オブジェクトを削除します。3. インポートにより、大規模なマージ通知がメイン スレッドによって処理され、UI が更新されます。4. 大規模なマージには、いくつかの影響が考えられます。 - UI が遅くなったり、忙しすぎて使用できなくなったりします。これは、 NSFetchedResultsControllerDelegateで tableviewを更新するためにbeginUpdates / endUpdatesを使用していることが原因である可能性があります、そして大規模なマージのために大量のアニメーションが待機しています。- ユーザーは、ストアから削除された障害のあるオブジェクトにアクセスしようとすると、「障害を満たせませんでした」というエラーに遭遇する可能性があります。管理対象オブジェクト コンテキストはそれが存在すると考えますが、フォルトを満たすためにストアに移動すると、既に削除されています。NSFetchedResultsControllerDelegate の tableview を更新するためにreloadDataを使用している場合、beginUpdates/endUpdates を使用する場合よりも、これが発生する可能性が高くなります。

上記の問題を解決するために使用しようとしているアプローチは次のとおりです。 - 2 つの NSPersistentStoreCoordinators を作成し、それぞれを同じ NSPersistentStore または少なくとも同じ NSPersistentStore SQLite ストア ファイル URL に接続します。- インポートは NSManagedObjectContext 1 で発生し、NSPersistentStoreCoordinator 1 にアタッチされ、他のスレッドで実行されます。NSFetchedResultsController は、NSPersistentStoreCoordinator 2 にアタッチされた NSManagedObjectContext 2 をメイン スレッドで実行しています。- 変更を NSManagedObjectContext 1 から 2 に移動しています

このアプローチでは、いくつかの問題が発生します。- NSPersistentStoreCoordinator の 仕事は、それが接続されている間を仲介することですNSManagedObjectContexts とそれに接続されたストアです。あなたが説明しているマルチコーディネーターコンテキストのシナリオでは、SQLiteファイルの変更を引き起こすNSManagedObjectContext 1による基礎となるストアへの変更は、NSPersistentStoreCoordinator 2とそのコンテキストには表示されません。2 は 1 がファイルを変更したことを認識していないため、「障害を実行できませんでした」などのエキサイティングな例外が発生します。- ある時点で、変更された NSManagedObjects をインポートから NSManagedObjectContext 2 に配置する必要があります。これらの変更が大きい場合でも、UI の問題が発生し、UI がストアと同期しなくなり、"障害を遂行できませんでした」。- 一般に、NSManagedObjectContext 2 は NSManagedObjectContext 1 と同じ NSPersistentStoreCoordinator を使用していないため、物事が同期していないという問題が発生します。これは、これらのものを一緒に使用することを意図した方法ではありません。NSManagedObjectContext 1 でインポートして保存すると、NSManagedObjectContext 2 はすぐにストアと一致しない状態になります。

これらは、このアプローチでうまくいかない可能性のあるものの一部です。これらの問題のほとんどは、ストアにアクセスするため、フォルトを発生させると明らかになります。このプロセスがどのように機能するかについては、コア データ プログラミング ガイドを参照してください。インクリメンタル ストア プログラミング ガイドでは、プロセスについて詳しく説明しています。SQLite ストアは、インクリメンタル ストアの実装と同じプロセスに従います。

繰り返しますが、あなたが説明しているユースケース - 大量の新しいデータを取得し、データに対してfind-Or-Createを実行して管理対象オブジェクトを作成または更新し、実際にはストアの大部分である可能性がある「古い」オブジェクトを削除します-あなたと同じ問題をすべて見て、私が数年間毎日対処してきたこと。一度に 60,000 個の複雑なオブジェクトを変更するインポートや、スレッドの制限を使用する場合でも、解決策があります。-しかし、それはあなたの質問の範囲外です。(ヒント: 親子コンテキストはマージ通知を必要としません)。

于 2013-10-16T19:58:45.773 に答える
3

2 つの Persistent Store Coordinator (pscs) は、確かに大規模なデータセットを処理する方法です。ファイルのロックは、コア データ内のロックよりも高速です。

バックグラウンド psc を使用して、バックグラウンドで実行する操作ごとに作成されるスレッド限定 NSManagedObjectContexts を作成できない理由はありません。ただし、コア データにキューイングを管理させる代わりに、NSOperationQueues やスレッドを作成して、バックグラウンドで実行していることに基づいて操作を管理する必要があります。NSManagedObjectContexts は無料で、高価ではありません。これを行うと、NSManagedObjectContext にハングアップし、その 1 つの操作および/またはスレッドの有効期間中にのみそれを使用し、必要な数の変更を作成し、最後まで待ってそれらをコミットし、それらをメインスレッドにマージすることができます。決めます。メインスレッドの書き込みがいくつかある場合でも、操作のライフタイムの重要な時点で、スレッドコンテキストに再フェッチ/マージすることができます。

また、大規模なデータ セットを扱っている場合は、他の何かに触れていない限り、コンテキストのマージについて心配する必要がないことを知っておくことも重要です。たとえば、クラス A とクラス B があり、それらに対して作業する 2 つの別個の操作/スレッドがあり、それらに直接的な関係がない場合、コンテキストをマージする必要はありません。この方法でバックグラウンド コンテキストをマージする唯一の主な必要性は、直接的な関係に問題がある場合です。NSOperationQueue であろうと他のものであろうと、ある種のシリアライゼーションを通じてこれを防ぐ方が良いでしょう。そのため、背景にあるさまざまなオブジェクトに自由に取り組んでください。ただし、それらの関係には注意してください。

私は大規模なコア データ プロジェクトに取り組んできましたが、このパターンは非常にうまく機能しました。

于 2013-10-17T19:24:07.703 に答える
1

実際、これはあなたが扱える最高のコア データ シナリオです。メイン UI の古さはほとんどなく、データのバックグラウンド管理が簡単です。メイン コンテキスト (およびおそらく現在実行中の ) に伝えたい場合NSFetchedResultsControllerは、次のように backgroundContext の保存通知をリッスンします。

    [[NSNotificationCenter defaultCenter] 
      addObserver:self selector:@selector(reloadFetchedResults:)
      name:NSManagedObjectContextDidSaveNotification
      object:backgroundObjectContext];

次に、変更をマージできますが、保存する前にメイン スレッド コンテキストが変更をキャッチするのを待ちます。通知を受け取ったmergeChangesFromContextDidSaveNotification時点では、変更はまだ保存されていません。したがって、performBlockAndWaitは必須であるため、メイン コンテキストが変更を取得してから、NSFetchedResultsControllerがその値を正しく更新します。

-(void)reloadFetchedResults:(NSNotification*)notification
{
    NSManagedObjectContext*moc=[notification object];
    if ([moc isEqual:backgroundObjectContext]) 
    {
        // Delete caches of fethcedResults if you have a deletion
        if ([[theNotification.userInfo objectForKey:NSDeletedObjectsKey] count]) {
            [NSFetchedResultsController deleteCacheWithName:nil];
         }
        // Block the background execution of the save, and merge changes before
        [managedObjectContext performBlockandWait:^{
            [managedObjectContext 
            mergeChangesFromContextDidSaveNotification:notification];
        }];
    }
}

誰も気づいていない落とし穴があります。マージするオブジェクトがバックグラウンド コンテキストによって実際に保存される前に、保存通知を受け取ることができます。バックグラウンド コンテキストによってまだ保存されていないオブジェクトを要求するより高速なメイン コンテキストによる問題を回避したい場合は、バックグラウンド セーブの前に呼び出す必要があります (本当にそうすべきです) 。その後、 を安全に呼び出すことができます。これにより、マージがマージ用の有効な永続 ID を受け取ることが保証されます。obtainPermanentIDsForObjects mergeChangesFromContextDidSaveNotification

于 2014-08-07T09:47:11.990 に答える