私は数週間取り組んできた問題を抱えています。Core Data マネージド オブジェクト コンテキストを保存するたびに、UI のパフォーマンスが低下します。私は自分でできる限りのことをしてきたので、助けを求めています。
状況
私のアプリケーションは 2 つのNSManagedObjectContext
インスタンスを使用します。1 つはアプリケーション デリゲートに属し、永続ストア コーディネーターがアタッチされています。Class
もう 1 つはメイン MOC の子で、 と呼ばれるオブジェクトに属しますPhotoFetcher
。NSPrivateQueueConcurrencyType
この MOC で実行されるすべての操作は、バックグラウンド キューで行われるように使用されます。
このアプリケーションは、API から写真に関するデータを表す JSON データをダウンロードします。API からデータを取得するために、次の一連の手順が実行されます。
NSURLRequest
オブジェクトを構築し、プロトコルを使用してNSURLConnectionDataDelegate
リクエストから返されたデータを構築するか、エラーを処理します。- JSON データのダウンロードが完了したら、次のことを行うセカンダリ MOC のキューでブロックを実行します。
NSJSONSerialization
Foundation クラスのインスタンスを使用して JSON を解析します。- 解析されたデータを反復処理し、必要に応じてバックグラウンド コンテキストでエンティティを挿入または更新します。通常、これにより、約 300 の新しいエンティティまたは更新されたエンティティが生成されます。
- バックグラウンド コンテキストを保存します。これにより、私の変更がメインの MOC に反映されます。
- メイン MOC でブロックを実行して、そのコンテキストを保存します。これは、データを
SQLite
ストアであるディスクに永続化するためです。最後に、応答が Core Data ストアに完全に挿入されたことを通知するデリゲートへのコールバックを行います。
バックグラウンド MOC を保存するコードは次のようになります。
[AppDelegate.managedObjectContext performBlock:^{
[AppDelegate saveContext]; //A standard save: call to the main MOC
}];
メイン オブジェクト コンテキストが保存されると、メイン オブジェクト コンテキストが最後に保存されてからダウンロードされたかなりの数の JPEG も保存されます。現在、iPhone 4 では、200x200 の JPEG を 15 個、圧縮率 70% で、合計で約 2MB のデータをダウンロードしています。
問題
これは機能し、うまく機能します。私の問題は、バックグラウンド コンテキストが保存NSFetchedResultsController
されると、View Controller での実行がメイン MOC に伝達された変更を取得することです。PSTCollectionView
のオープンソースクローンである に新しいセルを挿入しますUICollectionView
。新しいセルを挿入している間、メイン コンテキストはそれらの変更を保存してディスクに書き込みます。これは、iOS 5.1 を実行している iPhone 4 では、250 ~ 350 ミリ秒かかります。
その 3 分の 1 の間、アプリは完全に応答しません。保存前に進行中だったアニメーションは一時停止され、保存が完了するまで新しいユーザー イベントはメインの実行ループに送信されません。
Time Profiler を使用して Instruments でアプリを実行し、メイン スレッドをブロックしているものを特定しました。残念ながら、結果はかなり不透明です。これは、Instruments から得た最も重いスタック トレースです。
永続的なストアに更新を保存しているように見えましたが、確信が持てませんでした。そのため、MOC がディスクに触れないように呼び出しをまったく削除しsaveContext
ましたが、メイン スレッドでのブロッキング呼び出しはまだ存続しています。
テキスト形式のトレースは次のようになります。
Symbol Name
-[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:]
-[NSManagedObjectContext executeFetchRequest:error:]
-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]
_perform
_dispatch_barrier_sync_f_invoke
_dispatch_client_callout
__82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke_0
-[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:]
-[NSManagedObjectContext executeFetchRequest:error:]
-[NSPersistentStoreCoordinator executeRequest:withContext:error:]
-[NSSQLCore executeRequest:withContext:error:]
-[NSSQLCore objectsForFetchRequest:inContext:]
-[NSSQLCore newRowsForFetchPlan:]
-[NSSQLCore _newRowsForFetchPlan:selectedBy:withArgument:]
-[NSSQLiteConnection execute]
私が試したこと
Core Data コードに触れる前に、最初に行ったのは JPEG の最適化です。小さい JPEG に切り替えたところ、パフォーマンスが向上しました。次に、一度にダウンロードする JPEG の数を減らしました (90 から 15 に減らしました)。これは、パフォーマンスの大幅な向上にもつながります。ただし、メイン スレッドで 250 ~ 350 ミリ秒の長さのブロックがまだ見られます。
私が最初に試みたのは、バックグラウンドの MOC を取り除き、それが問題を引き起こしている可能性を排除することでした。実際、更新または作成コードがメイン スレッドで実行されていたため、アニメーション全体のパフォーマンスが低下していたため、事態はさらに悪化しました。
永続ストアを に変更しNSInMemoryStoreType
ても効果はありませんでした。
バックグラウンドで管理されたオブジェクト コンテキストが約束した UI パフォーマンスを実現する「秘密のソース」を誰か教えてもらえますか?