【Swift】Core Dataの使い方。異なる管理オブジェクトコンテキストで保存したデータをリレーションシップに設定する。(Swift 2.1、XCode 7.2)

2020年6月16日

複数の管理オブジェクトコンテキストの注意点

前回記事で、複数の管理オブジェクトコンテキスト(以下、コンテキスト)を使って保存タイミングをエンティティごとに変える実装方法を説明した。⇒「前回記事

複数の管理オブジェクトコンテキストを作るイメージ

 

1つ注意点がある。コンテキストに格納されているオブジェクトは、他のコンテキストに格納されているオブジェクトを直接参照できないことだ。

例えば、下図のように生徒オブジェクトと部活オブジェクトが参照し合う間柄であるとき、生徒と部活で異なるコンテキストを使うと、フェッチしたオブジェクトを直接別のオブジェクトのリレーションシップに設定できなくなる。⇒「リレーションシップとは

リレーションスップ

 

具体例を挙げると、以下のコードのように生徒エンティティに部活クラスのリレーションシップが定義されているとする。このリレーションシップに別のコンテキストでフェッチした部活オブジェクトを設定しようとすると、

 

以下のエラーが発生する。異なるコンテキストのオブジェクトをリレーションシップに設定しようとしたためのエラーだ。

*** Terminating app due to uncaught exception 'NSInvalidArgumentException’, reason: 'Illegal attempt to establish a relationship 'club’ between objects in different contexts

別コンテキストのオブジェクトをリレーションシップに設定する

別コンテキストのオブジェクトをリレーションシップに設定するには、下図のように、別コンテキストで保存されたデータを自分のコンテキストにフェッチしてリレーションシップに設定する。

リレーションシップを設定する流れ

 

上記の流れを実際に試してみよう。

検証に使用したXcodeプロジェクトをGitHubに置いたので、試してみる人はご利用されたし。
⇒「テスト用プロジェクト

生徒登録用と部活登録用の画面があり、下表はボタンの機能の説明。

生徒登録

ボタン 説明
「コンテキストに追加」ボタン 名前と所属部を設定したオブジェクトをコンテキストに追加する。同じ名前のオブジェクトがすでに存在する場合は、そのオブジェクトを編集する。
「保存」ボタン コンテキストに追加したオブジェクトを外部ファイルに保存する。
「生徒一覧表示」ボタン すべての生徒オブジェクトをフェッチして内容をデバッグエリアに一覧表示する。

 

部活動登録

ボタン 説明
「コンテキストに追加」ボタン 部名と顧問を設定したオブジェクトをコンテキストに追加する。同じオブジェクトがすでに存在する場合は、そのオブジェクトを編集する。
「保存」ボタン コンテキストに追加したオブジェクトを外部ファイルに保存する。
「部活一覧表示」ボタン すべての部活オブジェクトをフェッチして、内容をデバッグエリアに一覧表示する。

 

実際のプレイ動画

 

以下は検証プロジェクトのViewController.swiftのソースコード

 

まずは、部活オブジェクトを生徒オブジェクトのリレーションシップに設定する流れを試してみる。以下はその流れ。

  • 野球部を入力し「コンテキストに追加」ボタンを押す。(下図①)
  • 部活登録の「保存」ボタンを押す(②③)
  • 部活登録の「部活一覧表示」ボタンを押して、保存結果を確認する
  • 生徒の名前と所属部を入力し「コンテキストに追加」ボタンを押す(④⑤)
  • 生徒の「保存」ボタンを押す
  • 「生徒一覧表示」ボタンを押して、リレーションシップの正常登録を確認する

リレーションシップを設定する流れ

 

以下は実際のプレイ動画。保存した野球部の情報が、生徒オブジェクトのリレーションシップに正常に設定、保存された。

 

親子のコンテキストを利用する

上記の作りには1つ欠点がある。それは、リレーションシップに設定したオブジェクトの属性値を変更しても、リレーションシップから参照する値は変わらないことだ。

例えば、生徒オブジェクトのリレーションシップに「野球部、佐野」のオブジェクトを設定したあとに、部活設定画面で顧問を「佐野」から「鬼瓦」に変更したとする。

下図のように、「保存」ボタンを押すことで外部ファイルへの保存は正常に行われるが、生徒オブジェクトのリレーションシップを見ると「佐野」のまま変わらない。

なぜなら、先ほどフェッチされた野球部のオブジェクトは生徒用のコンテキストに存在しているので、参照するときにそのオブジェクトが利用されるからである。

部活のオブジェクトを更新してもリレーションのオブジェクトの値が変わらない

 

以下は実際のプレイ動画

 

アプリを再起動すれば、メモリがクリアされて外部ファイルから最新の値が取得されるのでリレーションシップのオブジェクトも「鬼瓦」になるが、アプリ起動中もリレーションシップの値は最新の値でないと困る。

そんなときに利用されるのが親子のコンテキストである。

親子のコンテキストとは、2つのコンテキストに親と子の関係を持たせ、子コンテキストの保存メソッドを呼び出すと、子コンテキストのオブジェクトが親コンテキストにマージされる機能である。

子コンテキストのオブジェクトが親コンテキストにマージされる

 

検証用プロジェクトのコンテキストを親子のコンテキストに変えてみよう。

AppDelegate.swiftの「部活用の管理オブジェクトコンテキスト」を以下のコードに変更する。

parentContextプロパティに生徒用のコンテキストを設定、コーディネーターを削除、キューをプライベートキューに変更した。

 

以下は実際のプレイ動画。顧問を「鬼瓦」に変更し、保存ボタンを押した段階で生徒オブジェクトのリレーションシップも「鬼瓦」になった。

 

マージされた子コンテキストのオブジェクトが外部ファイルに保存されるのは、親コンテキストの保存メソッドが呼び出されたときになるのも覚えておこう。