【Swift】Core Dataの使い方。Fetched Results Controllerでフェッチ結果を扱いやすくする(Swift 2.1、XCode 7.2)

2020年6月16日

Fetched Results Controllerとは

本記事ではCore DataのFetched Results Controller(以下、フェッチドリザルトコントローラー)について説明する。

フェッチドリザルトコントローラーとは、フェッチ結果と管理オブジェクトコンテキスト(以下、コンテキスト)を管理するコントローラーである。フェッチ結果の扱いやセクション分けを容易にしたり、コンテキストの変更を通知してくれたりする。

Fetched Result Controller

 

フェッチドリザルトコントローラーを試す

実際にフェッチドリザルトコントローラーを使ってみよう。

以降の手順を行う前のXcodeプロジェクトをGitHubに置いたので、試してみる人はご利用されたし。
⇒「テスト用プロジェクト

事前準備では、フェッチドリザルトコントローラーを使わずに、入力文字列を含むデータをフェッチして表示し、スワイプして削除できるように実装しておいた。これをフェッチドリザルトコントローラーを使う実装に変更する。

 

ViewController.swiftを以下のコードに変更する。

ViewDidLoadメソッドでフェッチドリザルトコントローラーのインスタンスを作り、そのインスタンスに対して各メソッドでフェッチ実行やデータ削除、保存を実行している。

フェッチドリザルトコントローラーがフェッチ結果を保持しているので、フェッチ結果の配列をViewControllerで保持する必要がなくなった。さらに、コンテキストが更新されるとフェッチ結果も自動で更新される。

ただし、フェッチ結果が自動で更新されるのはプロセスが終了してからなので、データ削除後のテーブルビュー更新はフェッチドリザルトコントローラーのデリゲートメソッド「コンテキスト変更時の呼び出しメソッド」で行っている。

 

以下は実際のプレイ動画。フェッチドリザルトコントローラーを使った実装に変更しただけなので、動き自体は変更前と変わらない。

 

 

セクション分けを行う

フェッチドリザルトコントローラーを生成するときの引数は4つある。この中の「セクション分けの属性」にエンティティの属性名を指定すると、指定した属性の値が同じものでセクション分けをしてくれる。セクション分けのロジックを考える必要が無くなるので便利だ。

NSFetchedResultsController(fetchRequest:フェッチリクエスト,
managedObjectContext: 管理オブジェクトコンテキスト,
sectionNameKeyPath: セクション分けの属性,
cacheName: キャッシュ名)

 

実際にセクションを分けをしてみよう。

ViewController.swfitのフェッチドリザルトコントローラーを生成する箇所を以下のコードに変更する。セクション分けの属性に「publisher(出版社)」を指定している。

 

ViewController.swiftに以下のメソッドを追加する。

フェッチドリザルトコントローラーのsectionsプロパティにセクション情報が設定されているので、このプロパティを利用してセクション数、セクション名を返している。

 

「コンテキスト変更時の呼び出しメソッド」を以下のコードに変更する。

セクションからデータがすべて無くなる場合、セクションを残したままにすると以下のようなエラーが発生する。

CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. Invalid update: invalid number of sections.

The number of sections contained in the table view after the update (5) must be equal to the number of sections contained in the table view before the update (6), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with userInfo (null) Terminating app due to uncaught exception 'NSInternalInconsistencyException’, ..省略

 

そこで、テーブルビューのセクション数と更新後のコンテキストのセクション数が異なる場合、該当セクションをテーブルビューから削除するようにした。

また、行とセクションを同時に消すときのデータの不整合を発生させないために、beginUpdates/endUpdatesメソッドで削除処理を囲った。

ちなみに、beginUpdatesメソッドを呼んでからendUpdatesメソッドを呼ぶまでのあいだは、テーブルビューのセルを削除してもインデックスは更新されなくなる。

 

以下は実際のプレイ動画