3

私は最近、Grand Central Dispatch を使用してバックグラウンドでフェッチとインポートを管理するように Core Data ドリブン データベース コントローラーを書き直しました。コントローラーは 2 つの NSManagedContext で操作できます。

  1. NSManagedObjectContext *mainMocメインスレッドのインスタンス変数。dipatch_get_main_queue()このコンテキストは、メイン スレッドまたはグローバル キューによる UI へのクイック アクセスでのみ使用されます。

  2. 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 関数の終わり

bgMocmainMocを使用して保存時にマージします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 つの理由から、このパターンが完全にクリーンではないことはわかっています。

  1. bgQueue中断後に必ずしも同じスレッドを起動するとは限りませんが、シリアルであるため、それほど重要ではありません (2 つのスレッドがbgMoc専用の NSManagedObjectContext にアクセスしようとすることは決してありません)。
  2. テーブル ビューのデータ ソース メソッドは、NSFetchedResultsController に bgMoc からの情報 (フェッチは bgQueue で行われるため) を要求することがあります (セクション カウント、セクション カウント内のフェッチされたオブジェクトなど....

この欠陥がある場合でも、このアプローチはアプリケーション実行時間の 95% でかなりうまく機能します...

そして、ここに私の質問があります:

ときどき、非常にランダムにアプリケーションがフリーズしますが、クラッシュはしません。どのタッチにも応答せず、ライブに戻す唯一の方法は、完全に再起動することです (バックグラウンドに切り替えたり、バックグラウンドから切り替えたりしても役に立ちません)。例外はスローされず、コンソールには何も出力されません (Xcode ですべての例外に対してブレークポイントが設定されています)。

メインスレッドで何か難しいことが起こっているかどうかを確認するために、Instruments (特に時間プロファイル) を使用してデバッグしようとしましたが、何も表示されません。

ここで GCD と Core Data が主な容疑者であることは承知していますが、これを追跡/デバッグする方法がわかりません。

これは、すべてのタスクを非同期的にのみキューにディスパッチした場合にも発生することを指摘しておきます (どこでもdispatch_asyncを使用)。これは、単なる標準的なデッドロックではないと思います。

どうすれば何が起こっているのか、より多くの情報を得ることができる可能性やヒントはありますか? いくつかの追加のデバッグ フラグ、楽器の魔法のトリック、ビルド設定など...

NSFetchedResultsController のバックグラウンドフェッチとバックグラウンドインポートをより良い方法で実装する方法へのポインタと同様に、何が原因である可能性があるかについての提案は非常に高く評価されています。

4

2 に答える 2

3

私の最初の非常に悪い間違いはNSFetchedResultsController、バックグラウンド キューでデータをフェッチすることでした。

テストの結果、私は取得時間に神経質になりすぎていたことが判明しました。私fetchDataが生成できる最長のフェッチ時間は文字通り一瞬でした。これにより、非常に小さなパフォーマンスの向上 (もしあれば) に対して、あまりにも多くの複雑さと不確実性が導入されました。

fetchData の実行とすべての NSFetchedResultsControllerDelegate メソッドをメイン スレッドに移動することで、フォームを辞任しました (GCD コードを削除してコードを簡素化しました)。

これが完了すると、メインスレッドコンテキストmainMocDidSave:のリッスンが 不要になり、登録が解除されました。NSManagedObjectContextDidSaveNotificationDATABASE_SAVED_WITH_CHANGES 通知の投稿を削除して登録解除することもできました。

この大幅に簡素化された「マージ」メカニズムは、今回からバックグラウンド スレッド コンテキストのみが変更をメイン スレッド コンテキストとマージするためです (保存時)。これを一方向変更通知と呼びましょう。

NSFetchedResultsControllerDelegateメソッドは、マージ後にメイン スレッド コンテキストの変更を取得するときに自動的に起動されます。

もう 1 つの重要なことは、次の中で dispatch_async をdispatch_syncに変更することです。

- (void)bgMocDidSave:(NSNotification *)saveNotification {

    // CDdb - bgMoc didsave - merging changes with main mainMoc
        // Previously was: dispatch_async
        // dispatch_sync in this place may prevent from overlapping merging in some cases (many blocks in background queue)
    dispatch_sync(dispatch_get_main_queue(), ^{

    [self.mainMoc mergeChangesFromContextDidSaveNotification:saveNotification];
     // !!! Extra notification NO needed anymore

    });
}

経験則:スレッドと NSManagedContext の量を単純化して最小化します。非常に大きなアプリでも、2 つのコンテキストで十分であることを経験しました。

  1. GCD キュー専用で動作する importContext (シリアル キューである必要があります)。キュー ブロックのコードの最後に保存することを忘れないでください。
  2. UI (プレゼンテーション) のデータをプルするときに使用するメイン UI スレッド (私はそれを READ コンテキストと呼びます ;-) で動作するための mainConstext。
于 2012-09-19T21:31:10.240 に答える
1

DATABASE_SAVED_WITH_CHANGES 通知は少し疑わしいように見えます: bgMoc が保存するとしましょう。次にbgMocDidSave:、変更を起動して mainMoc とマージしますが、これは問題ありません。次に、最終的に ( mainMocDidSave:DATABASE_SAVED_WITH_CHANGES が発生したときに発生すると仮定して) 通知を発生させ、変更を bgMoc (元の場所です!) にマージします。これは私にとって正しいアプローチのようには聞こえません。bgMocDidSave:また、通知が bgMoc から発信されていることを確認することもできます。mainMoc が保存されると、変更bgMocDidSave:も発生します。

于 2012-05-16T08:48:16.600 に答える