9

私たちは、コア データを使用して数万のオブジェクトを格納するエンタープライズ レベルのアプリケーションに取り組んでおり、いくつかの面で問題を抱えています。


私たちのアプリケーションには、必要に応じてデータを操作するいくつかの独立したシステムがあります。これらのシステムには、アイテムの検出、アイテムのロード、同期、および UI 表示が含まれます。ソフトウェアを正しく設計すれば、異なるシステムが同じオブジェクトを変更することによるマージの競合はほとんどまたはまったくないはずです。各システムには独自の操作キューがあり、すべてバックグラウンドで実行されます。UI パフォーマンスの問題を最小限に抑えるために、すべてのオブジェクトの作成と変更をバックグラウンドに保持したいと考えています。特に、サーバー上のデータから何千ものオブジェクトが作成される可能性がある初期のランプアップ中はそうです。ここで、さまざまな設計の試みでいくつかの問題にぶつかりました。これらのランプアップ中の大量のメモリ消費、およびすべてのコンテキストと子コンテキストの不適切なオーケストレーションにより、デッドロックとクラッシュが発生します。以下の設計を試みました。

  • 1 つの子コンテキストを持つ 1つのルートNSPrivateQueueConcurrencyType管理オブジェクトNSMainQueueConcurrencyTypeコンテキスト。UI フェッチ結果コントローラーは、この子コンテキストを使用して結果をフェッチします。子コンテキストから、 「savingContext」と呼ばれるNSMainQueueConcurrencyType1NSPrivateQueueConcurrencyTypeつの子コンテキストを作成し、各バックグラウンド操作によってその「savingContext」の子コンテキストが作成され、その変更が行われ、最後に「ディープ セーブ」と呼ばれるものが実行され、再帰的に保存されます。トップ。最初にこの設計を選択したのはNSManagedObjectContextDidSaveNotification、多くの異なる子コンテキストからの通知を処理する必要がないようにするためです。NSPrivateQueueConcurrencyTypeコンテキストへのすべての呼び出しとオブジェクトへのアクセスをperformBlockAndWait:. 機能的に、このデザインは機能しました。すべての変更と挿入は永続ストアに保存され、変更によって UI が更新されました。これにより、2 つの問題が発生しました。1 つは、マージされた変更がNSMainQueueConcurrencyType子コンテキストを通過するため、ランプアップ中に UI の遅延が発生し、さらに重要なことに、ランプアップ中のメモリ使用量が非常に高かったことです。resetコンテキストで再帰的に呼び出すことができないため (メイン UI の子コンテキストも存在するため)、および/または を呼び出すタイミングに関する知識が不足しているため、RAM の使用量が極端に多くなりますrefreshObject:mergeChanges:。そこで私たちは別の道を歩みました。
  • 永続ストア コーディネーターにリンクされた 2 つのトップレベル コンテキストを用意します。1NSPrivateQueueConcurrencyTypeつは子コンテキストの保存用、もう 1 つNSMainQueueConcurrencyTypeは UI 表示用です。はメイン コンテキストからの通知をNSMainQueueConcurrencyTypeリッスンし、それらをメイン スレッドにマージします。各バックグラウンド操作は、メインコンテキストの子コンテキストを作成し、これもプライベート キュー同時実行タイプを使用して実行し、現在のコンテキストで保存を実行する「ディープ セーブ」を再帰的に実行し、親へのディープ セーブの再帰呼び出しを実行します。 、現在のコンテキストでリセットを呼び出し、再度保存します。このようにして、作成されたオブジェクトは保存後にすぐに解放されるため、メモリの問題を回避できます。ただし、この設計では、デッドロック、NSManagedObjectContextDidSaveNotificationNSPrivateQueueConcurrencyTypeNSPrivateQueueConcurrencyTypeNSInternalInconsistencyExceptionNSMainQueueConcurrencyTypeコンテキストの保存通知があるにもかかわらず、例外とフェッチされた結果コントローラーが UI を更新しない。これにより、UI の初期ロード時間が大幅に遅くなります。以前の設計では、フェッチされた結果コントローラーは非常に高速に結果を返しましたが、ビューが読み込まれるまで UI が数秒間ブロックされていました (フェッチされた結果コントローラーを で初期化しますviewDidLoad)。

多くの中間設計を試しましたが、それらはすべて同じ問題を中心に展開しています。メモリ使用量が非常に多い、フェッチされた結果コントローラーが UI を更新しない、またはデッドロックとNSInternalInconsistencyException例外です。


私は本当にイライラしています。私たちのデザインは、かなりシンプルであるべきものに対して、あからさまに複雑であるかのように感じずにはいられません。私たちを殺しているのは、基本的なことを理解していないためです。


それで、あなたたちは何を提案しますか?私たちのコンテキストにはどのような配置をお勧めしますか? 異なるスレッドで異なるコンテキストをどのように管理する必要がありますか? 挿入されたオブジェクトを解放し、コンテキストをリセットするためのベスト プラクティスは? デッドロックを回避しますか? この時点で、すべての助けをいただければ幸いです。


MagicalRecords カテゴリの推奨事項も確認しました。おすすめですか?Core Data タイプの使用にすでに投資していますが、MR を使用して移行するのはどのくらい難しいでしょうか?

4

1 に答える 1

6

第 1 に、メモリを管理するために、第 2 のアーキテクチャでは柔軟性が大幅に向上します。

次に、管理するメモリには、malloc メモリと常駐 VM メモリの 2 種類があります。malloc されたメモリ フットプリントを低く抑えながら、大きな VM 常駐リージョンを維持できます。これは、私の経験では、Core Data が新しく挿入されたアイテムを積極的に保持しているためです。この問題は、保存後のトリミング通知で解決します。

第三に、MOC は安価です。使って捨てる。つまり、メモリを早期かつ頻繁に解放します。

第 4 に、メインの MOC ではデータベースに関してほとんど何もしないようにします。はい、これは逆効果に聞こえます。つまり、複雑なクエリはすべてバックグラウンド スレッドで実行してから、結果をメイン スレッドに渡すか、メイン スレッドからクエリをやり直して、データが取り込まれた行キャッシュを活用する必要があるということです。これにより、UI をライブに保つことができます。

5 番目に、非常にマルチキューされたアプリで、すべての保存が実際にバックグラウンドで行われるようにします。これにより、メインの MOC が高速になり、ネットからのデータとの一貫性が保たれます。

第 6 に、NSFetchedResultsController は非常に便利ですが、特殊なコントローラーです。アプリケーションがそれをその能力の範囲外に押し出すと、インターフェイスがロックされ始めます。その場合は、自分で -didSave 通知をリッスンして自分のコントローラーをロールします。

于 2012-11-03T16:54:47.080 に答える