導入: コアデータとNSFetchedResultsControllerのマッター

現代のiOS開発では、流体、応答性のあるユーザーインターフェイスを頻繁に提供することは、アプリが時間とともに変化するデータを処理する方法によって異なります。 ソーシャルフィード、タスクマネージャー、または在庫システムを構築するかどうかにかかわらず、ユーザーに表示されるデータは、ほとんど静的ではありません。 コアデータ — アップルの成熟したオブジェクトグラフと永続フレームワーク — (FRC) と組み合わせて、動的で大規模なデータセットを管理するための戦闘テストソリューションを提供しています。 手動でUIを同期することなくUIを同期させることなく、UIを同期させる。

この記事では、Core Dataを通知で使用するための詳細な、実用的なガイドを提供します。 オブジェクトが入力、更新、または削除されると、コンテキストはこれらの変更を放送し、正確にがあなたのUIを一貫性保つために活用します。

正式な開発者向けドキュメントについては、 []] のAppleのコアデータフレームワークリファレンス[ を参照してください。

NSFetchedResultsController:コアデータとUIのブリッジ

[は、コアデータフェッチリクエストから返された結果を効率的に管理するために設計されたコントローラオブジェクトです。特に、過渡データが変更されると想定されます。 関連する管理されたオブジェクトのコンテキストを監視し、自動的にそのデレゲートプロトコルを介して変更を報告します。

主な特長

  • ]自動変更トラッキング:] コンテキスト通知を聞き、構造化されたデリーゲートコールバック()、[、[])に変換します。
  • []ビルトインセグメント:[]を指定することで、コントローラグループは、セクションテーブルビューやコレクションビューを表示しようとします。
  • [Performanceの最適化:]]]は、FACは、フードの下に障害とキャッシュを使用します。 必要に応じてデータを取得し、管理されたオブジェクトのコンテキストが保存されるときに再取得を避けるために、永続的なキャッシュを使用することができます。

詳細のメソッドを委任する

コントローラーから完全に利益を得るためには、を実装しなければなりません。最も一般的なパターンは、または内のコールバックを使用することです。 バッチ更新UIに委任します。 たとえば:

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
 tableView.beginUpdates()
}

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
 didChange anObject: Any,
 at indexPath: IndexPath?,
 for type: NSFetchedResultsChangeType,
 newIndexPath: IndexPath?) {
 switch type {
 case .insert:
 tableView.insertRows(at: [newIndexPath!], with: .fade)
 case .delete:
 tableView.deleteRows(at: [indexPath!], with: .fade)
 case .update:
 tableView.reloadRows(at: [indexPath!], with: .fade)
 case .move:
 tableView.moveRow(at: indexPath!, to: newIndexPath!)
 @unknown default:
 tableView.reloadData()
 }
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
 tableView.endUpdates()
}

このパターンは、テーブルビューが、下流データと同期して変化をアニメーション化し、フリッカーや不整合を防ぐことができます。

Step-by-Step の実装

以下は、iOS 15 + をターゲットとする Swift 5 を使用して、完全な、生産準備例です。 属性 ] と (Bool) 、 (Date) という単純なエンティティティを想定します。

1. コアデータスタックの設定

または ] (SwiftUI アプリで共通) で、永続コンテナを作成します。

class PersistenceController {
 static let shared = PersistenceController()
 let container: NSPersistentContainer

 init() {
 container = NSPersistentContainer(name: "YourModelName")
 container.loadPersistentStores { storeDescription, error in
 if let error = error as NSError? {
 fatalError("Unresolved error \(error), \(error.userInfo)")
 }
 }
 container.viewContext.automaticallyMergesChangesFromParent = true
 }
}

2. NSManagedObject サブクラスを作成する

Xcode のデータモデルエディタを使用して、クラスファイルを生成したり、手動で作成したりします。 実体が正しいクラスモジュールの設定を使用することを確認してください。

3. パッチリクエストとFRCの設定

ビューコントローラーでは、フェッチリクエストを設定し、フェッチされた結果コントローラーを初期化します。これは]でこれを実行するのが最善です。ビューモデルの init です。

lazy var fetchedResultsController: NSFetchedResultsController<Task> = {
 let fetchRequest: NSFetchRequest<Task> = Task.fetchRequest()
 let sortDescriptor = NSSortDescriptor(key: "dueDate", ascending: true)
 fetchRequest.sortDescriptors = [sortDescriptor]
 // Optional: limit results with batch size for large datasets
 fetchRequest.fetchBatchSize = 20

 let controller = NSFetchedResultsController(
 fetchRequest: fetchRequest,
 managedObjectContext: PersistenceController.shared.container.viewContext,
 sectionNameKeyPath: "completionStatus", // e.g., a transient attribute or a computed property
 cacheName: nil
 )
 controller.delegate = self
 try? controller.performFetch()
 return controller
}()

4. FRC でテーブルビューをドライブする

[ メソッドは、次のようにトリガーされます。

func numberOfSections(in tableView: UITableView) -> Int {
 fetchedResultsController.sections?.count ?? 0
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
 let sectionInfo = fetchedResultsController.sections![section]
 return sectionInfo.numberOfObjects
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
 let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath)
 let task = fetchedResultsController.object(at: indexPath)
 configure(cell, with: task)
 return cell
}

手動で[を呼び出してはいけないことに、すべてのインサート、削除、および更新を処理するデリゲートメソッドは、決して呼び出しません。

高度な使用とベストプラクティス

スレッドの安全

コアデータコンテキストはスレッドセーフではありません。常に (メインキュー上で実行) を使用して、すべての UI 関連のフェッチリクエストと FRC 。背景作業では、プライベート キューのコンテキストを作成し、ビューのコンテキストに変更をマージします。複数のキューを渡る同じ FRC を使用するのを避けてください。

キャッシュ

[パラメータは、行列とオブジェクト情報によって起動性能を向上させることができます。ただし、フェッチリクエストやデータモデルの変更があった場合は、破損を避けるためにキャッシュ()を削除する必要があります。

大容量データセットの処理

を使用して、メモリに取得されたオブジェクトの数を制限します。また、すべてのプロパティに即時アクセスする必要がある場合にのみ、 を設定することを検討してください。 関係集約データの場合、 で優先して、繰り返しの欠陥を防ぐことができます。

SwiftUIとの統合

はUIKit-セントリックですが、SwiftUIでは]にラップするか、新しい[プロパティラッパー(内部的に同様のメカニズムを使用する)を使用して、SwiftUIで使用できます。 SwiftUIの複雑な動的データの場合、 は通常十分ですが、FRCはアニメーションとバッチ処理の細かい-グラインド制御を提供します。

一般的な落札とテムを避ける方法

  • []Forgetting ]:[[]]]]] は、このメソッドを呼び出すまでフェッチを実行しません。 初期化直後に、それを繰り返します。
  • [] 区切りなしの区切り文字の設定はしない。テーブルビューに変更が伝播しない。 を常に呼び出します。
  • [] 間違ったソート記述子:[] が、セクション名キーパスが最初のソート記述子に一致しない場合、セクションは順に表示されることがあります。 ] で使用される属性は、ソート記述子に表示されます。
  • ] FRC の を 使っている ] は、FRC が変更を アニメーション化しているときに、 クラッシュを引き起こす可能性があります。 委任方法がすべての UI の更新を処理するようにします。
  • []コアデータコンテキストの保存失敗を無視する:[[]]])コンテキストを保存し、エラーが発生した場合は、FRCは通知されないことがあります。 常にエラーを扱い、背景文脈でを使用して検討してください。

パフォーマンスの考慮事項

は既に有効ですが、以下は追加の最適化です。

  • [] は、優先的に:[ を使います。複雑な述語は初期のフェッチを遅くすることができます。 可能なインデックス付き属性を使用してください。
  • ]取得されたプロパティを省略します:[]]。特定の属性のみが必要な場合は、フェッチリクエストにを設定してください。
  • []バッチ更新:]]]] 変更が多い場合は、 ブロックでそれらをラップして、デリーゲートコールバック数を減らす。
  • []不必要な障害:[]]を無効にした場合、結果セット内のすべてのオブジェクトにアクセスできる場合は、[]を使用して、それらを事前フェッチしますが、メモリに注意してください。

コアデータ性能を深く掘り下げるには、] アップルのコアデータパフォーマンスガイド を参照してください。

実世界事例

例1:チャットアプリケーション

メッセージングアプリは、最新のメッセージでソートされた会話のリストを表示します。新しい着信メッセージは即座に表示されます。 FRC を使用して、連絡先ビューは、現在のユーザーの会話エンティティティのみの変更を購読できます。 日付グループメッセージで「今日」、 "昨日" などセクションします。

例2:在庫管理

e-コマースアプリは、カテゴリに製品を表示します。 在庫レベルがバックグラウンド同期から変更されると、FRCはUIを自動的に更新します。 を50に設定することで、何千ものアイテムでもビューが応答します。

例3:カテゴリでリストをTo-Do

古典的な例: "Overdue", "Today", "Upcoming" にグループ化されたタスク。 [] は、 [] と現在の日付から計算された一時的な派生した属性であることができます。 同じ派生した値は、ソート記述子で不正な値を避けるために使用されます。

コンテンツ

Core Dataとの組み合わせをマスターするのは、堅牢で動的iOSアプリケーションを構築するコーナーストーンです。 変更トラッキングとUIの同期をAppleのフレームワークに重ねたリフティングをオフロードすることで、ボイラープレートのデータ管理コードを書くのではなく、素晴らしいユーザーエクスペリエンスを作成することに集中できます。

従来のUIKitアプリを維持しているか、SwiftUIをで採用しているかにかかわらず、原則は同じままです。オブジェクトグラフを理解し、フェッチリクエストを慎重に設定し、フェッチされた結果コントローラーが最善を尽くそうとします。この記事で説明した慣行では、キャッシング、スレッドセーフティ、パフォーマンスチューニングなど、時間をかけて進化するあらゆるデータセットを処理するのがうまくいきます。

さらなる調査については、 [] レイ・ウィンダーリックのコア・データ・チュートリアル と []]]] の NSFetchedResultsController[ に関するNSHipsterの記事を参照してください。