パフォーマンスを念頭に置いてコアデータコードを書くことは、将来のためにあなたのアプリを準備するのに役立ちます。 データベースは最初は小さいかもしれませんが、簡単に増加する可能性があるため、クエリが遅くなり、ユーザーの経験が低下します。
私は2017年にCollect by WeTransferアプリを書き始めて以来、ほとんど毎日それに触れて、コアデータ関連のコードをたくさん書いてきました。 何百万人ものユーザーが大量のコンテンツを追加しているため、コアデータ関連のコードを実行することは、私たちのチームの重要なスキルとなっています。
長年にわたり、私たちは多くの洞察を開発しました。
1: バックグラウンドマネージドオブジェクトコンテキストを使用する
最初からしなかったことの一つは、バックグラウンドマネージドオブジェクトコンテキストを使用することです。 新しいコンテンツの挿入、コンテンツの削除、コンテンツの取得など、コアデータ関連のタスクを実行するためにのみ、ビューコンテキストを使用しました。
当初、我々のアプリは比較的小さかった。 ビューコンテキストのみを使用することは実際には問題ではなく、コアデータに関連する目に見えるパフォーマンスの低下にはなりませんでした。 明らかに、アプリが成長し始めると、ビューコンテキストがメインキューに関連付けられていることに気付きました。 遅いクエリは、私たちのUIをブロックし、我々のアプリはあまり応答になりました。
一般に、CPUを大量に消費する可能性があるため、バックグラウンドキューでデータ処理を実行することをお勧めします。 JsonをCore Dataにインポートするなどの例では、ビューコンテキストがブロックされ、ユーザーインターフェイスが応答しなくなる可能性があります。
解決策は、バックグラウンドマネージドオブジェクトコンテキストを利用することです。 最新のApiを使用すると、永続コンテナから新しいコンテキストを簡単に作成できます:
let backgroundContext = persistentContainer.newBackgroundContext()
このメソッドはNSManagedObjectContext(concurrenyType:)
初期化子よりも自動的にNSPersistentStoreCoordinator
に関連付けられ、NSManagedObjectContextDidSave
ブロードキャストも消費するように設定されるため、このメソッドをお勧めします。 これにより、背景コンテキストとビューコンテキストの同期が維持されます。
このバックグラウンドコンテキストは、カスタムの永続コンテナサブクラスに保存できます。 このようにして、背景コンテキストを再利用することができ、2つのコンテキストのみを管理する必要があります。 これにより、コアデータ構造を理解するのが簡単になり、複数の同期外コンテキストを持つことがなくなります。
いくつかの場所で背景コンテキストを使用する必要がある場合は、背景コンテキストを作成するperformBackgroundTask(_:)
メソッドを使用することもできます:
persistentContainer.performBackgroundTask { (backgroundContext) in // .. Core Data Code}
ただし、このメソッドは、呼び出されるたびに新しいNSManagedObjectContext
を作成します。 背景コンテキストに頻繁にディスパッチする場合は、共有背景コンテキストの使用を検討することをお勧めします。
重要:キュー間でNSManagedObjectインスタンスを渡さない
マルチスレッドコアデータコードを書くことは、単一のビューコンテキストを使用するよりもはるかに複雑です。 この理由は、ビューコンテキストからバックグラウンドコンテキストにインスタンス化されたNSManagedObject
を単に渡すことができないためです。 これを行うと、クラッシュや潜在的なデータ破損になります。
マネージオブジェクトをあるキューから別のキューに移動する必要がある場合は、スレッドセーフであるNSManagedObjectID
を使用できます:
let managedObject = NSManagedObject(context: persistentContainer.viewContext)backgroundContext.perform { let object = try? backgroundContext.existingObject(with: managedObject.objectID)}
2: 管理オブジェクトコンテキストを保存する必要がある場合にのみ
管理オブジェクトコンテキストを保存すると、現在のすべての変更がコンテクストの親ストアにコミットされます。 ご想像のとおり、これは安価な操作ではなく、Core Dataのパフォーマンスを確保するために必要な場合にのみ使用する必要があります。
まず、保存するものがあるかどうかを確認することが重要です。 コミットする変更がない場合は、保存を実行する理由もありません。
変更を保存する時期を慎重に検討する
save()
の代わりにsaveIfNeeded
を使用することとは別に、保存が理にかなっているかどうかも考慮する必要があります。 コンテキストには変更がある可能性がありますが、これらの変更を直接コミットする必要はありません。
たとえば、データベースに重要な複数の項目がある場合は、バックグラウンドコンテキストにすべての項目をインポートした後にのみ保存することができます。 多くの場合、保存の後にUIが更新され、複数の保存が行われると、不要なリロードが簡単に発生する可能性があります。 それに加えて、バックグラウンドコンテキストで保存された変更がビューコンテキストにマージされ、メインキューもすぐにブロックされることを したがって、意識してください!
3: 必要なものだけを取得する
データを取得することは高価な作業であり、アプリを大規模なデータセット用に準備するためには、できるだけパフォーマンス 次のコードはしばしば間違いです:
このコードは、挿入されたすべてのオブジェクトをメモリにロードし、名前を持つコンテンツのみを保持するために、直後にフィルタリングされます。
述語を使用して、必要なオブジェクトのみをフェッチする方がはるかにパフォーマンスが高くなります。 上記のフィルタは、次のように書くことができますNSPredicate
:
これには2つの利点があります:
- 必要なオブジェクトのみがメモリにロードされます
- すべてのオブジェクトを反復する必要はありません
述語は非常に柔軟であり、コアデータのパフォーマ
4:フェッチ制限を利用する
前の例に続いて、データセットの一部のみを表示する場合は、フェッチ制限を設定することが重要です。
たとえば、すべてのコンテンツアイテムの最初の3つの名前だけが必要だとします。 この場合、名前を持つすべてのコンテンツアイテムをメモリにロードする必要はありません。 フェッチ制限を設定することでこれを防ぐことができます:
このコードは、名前を持つ最初の3つのコンテンツ項目のみを返します。
5:NSBatchDeleteRequest
を使用して一度に多くのオブジェクトを削除するdatasetを反復処理する代わりに、各オブジェクトを1つずつ削除します。NSBatchDeleteRequest
を使用する方が、永続的なストアレベルで動作するため、より高速に動作するNSBatchDeleteRequest
を使用する方がパフォーマンスが高いことがよくあります。
バッチ削除要求の詳細については、私のブログ記事でNSBatchDeleteRequestを使用してコアデータのバッチを削除することができます。
6:コアデータコードのデバッグ方法を知っている
書くすべてのコードと同様に、期待どおりに実行されないと、最適化してデバッグする方法を知っていることが重要です。 私の専用のブログ投稿で最もよく説明されているデバッグの多くの方法があります:起動引数を使用したXcodeでのCore Data Debugging。
結論
パフォーマンスの高いコアデータコードを最初から記述すると、将来の大規模なデータセットに備えてアプリを準備するのに役立ちます。 アプリは最初に実行されている可能性がありますが、データベースとモデルが大きくなると簡単に速度が低下する可能性があります。 バックグラウンドコンテキスト、スマートフェッチ要求、バッチ削除要求を利用することで、コアデータコードのパフォーマンスが向上しています。
Swiftの知識をさらに向上させたい場合は、Swiftカテゴリページをチェックしてください。 あなたが任意の追加のヒントやフィードバックを持っている場合は、私に連絡したり、Twitterで私につぶやくこと自由に感じ
ありがとう!