[XCODE] CoreDataにおいてテーブル定義変更を行う方法
今日は、iPhone開発ネタのうち、CoreDataのお話です。
CoreDataを用いたデータの永続化は、多くのアプリで行われていると思います。
しかし、一度テーブル定義を行ってから、その後テーブル定義変更を行い、その後アプリを 実行するとエラーが。そのエラーを発生させず、定義変更後にデータを移行する方法を 今日は書きたいと思います。
既にリリースしているのですが、このたびテーブルを一つ追加したいと思いました。
その際に、既に定義済みの定義ファイルを変更すると以下のようなエラーが発生します。
たくさんのデバッグ情報が出ますが、「reason = "Can't find model for source store"」が気にする部分のようです。
このエラーが出た際に、iPhone実機やシミュレーター上に前のビルドから残っているアプリを消してから、 もう一度起動するという手も新規開発中ならOKですが、既にリリースしたアプリの場合には、 CoreDataのモデルのバージョン移行が必要になります。
なお今回は、既にver1とver2のCoreDataが存在する状態で、ver1→ver2, ver1→ver3にデータを移行する例となっております。
初めてver2を作る場合も、一緒なので参考になると嬉しいです。
続いて、モデル名を記入して、拡張するベースとなるモデルを選択します。
ここで記入するモデル名は、バージョン番号などを入れると良いかもしれません。
Finishボタンをクリックすると、以下のようにxcdatamodelIdの下に、作成したモデルバージョンが追加されます。
以下の例では、新しく「NewEntity」というテーブルを新規追加しています。
ここで重要なのは、ver1, ver2が既にある場合には、ver1→ver3、ver2→ver3というように、既存のバージョン全てから 今回追加したバージョンへ移行する為のファイルを記載する必要がある点です。
ユーザーによっては、アプリをUpdateしてなくて、ver1のデータモデルのアプリを使い続けてるかもしれないので。
移行定義を行うファイルは、「File > New > File...」から追加できます。
ファイル追加ダイアログを開いたら、CoreDataにあるMapping Modelを選択し、Nextボタンを押します。
次のページでは、移行元のバージョンを選択します。今回は、ver1を移行元バージョンとして選択しています。
次に、移行先のバージョンを選択します。今回は、ver3を移行先として選択します。
最後にファイル名を入力します。ファイル名は自由に設定出来ますが、ver1_to_ver2などの移行元と先が分かると便利かもしれません。
作成すると以下のような内容のファイルが作成されます。自動的に移行元と移行先が定義されますが、変更したい場合には 自分で変更することもできます。
CoreDataを読み込む部分(XCODEプロジェクトで作るとApplicationDelegateを実装したクラスの中とか)で、 読み込む処理を変更します。
(変更前のソースコード)
上記のソースコードのうち、「addPersistentStoreWithType:configuration:URL:options:error:」メソッド呼び出しの部分で、 optionsを指定して、データ移行を行うように設定します。
変更後のソースコードは以下となります。
NSDictionaryでoptionsを作成して、自動的にデータ移行を行うように設定しています。
Core Data Versioning.pdf@Apple Developer
自身のアプリを進化させるべく、今後もiPhoneアプリ開発を頑張利隊と思います。
そこで得た情報をまたブログに記載させて頂きたいと思います。
いつもの2倍以上の記事量でしたが、最後までお読み頂きまして本当にありがとうございます。
CoreDataを用いたデータの永続化は、多くのアプリで行われていると思います。
しかし、一度テーブル定義を行ってから、その後テーブル定義変更を行い、その後アプリを 実行するとエラーが。そのエラーを発生させず、定義変更後にデータを移行する方法を 今日は書きたいと思います。
CoreDataのデータ移行(マイグレーション)の必要性
私がリリースしているiPhoneアプリの中に、出費管理を行う Pocket.Money.Managementがあります。 このアプリでは、出費情報(日付、内容、金額)をCoreDataを用いて保存して、参照するアプリです。既にリリースしているのですが、このたびテーブルを一つ追加したいと思いました。
その際に、既に定義済みの定義ファイルを変更すると以下のようなエラーが発生します。
2012-05-08 08:31:10.883 PMP_tmp[1180:fe03] Unresolved error Error Domain=NSCocoaErrorDomain Code=134130 "The operation couldn’t be completed. (Cocoa error 134130.)" UserInfo=0x6b8e5f0 {URL=file://localhost/Users/someone/Library/Application%20Support/iPhone%20Simulator/5.1/Applications/E7154F8A-8145-40CE-8ED3-FEF82D39055D/Documents/PocketMoneyManagement.sqlite, metadata=<CFBasicHash 0x6b71920 [0x1ed8b48]>{type = immutable dict, count = 7,
entries =>
2 : <CFString 0x6b71b00 [0x1ed8b48]>{contents = "NSStoreModelVersionIdentifiers"} = <CFArray 0x6b721f0 [0x1ed8b48]>{type = immutable, count = 1, values = (
0 : <CFString 0x1ed3cd8 [0x1ed8b48]>{contents = ""}
)}
4 : <CFString 0x6b71d30 [0x1ed8b48]>{contents = "NSPersistenceFrameworkVersion"} = <CFNumber 0x6b71d90 [0x1ed8b48]>{value = +386, type = kCFNumberSInt64Type}
6 : <CFString 0x6b71d60 [0x1ed8b48]>{contents = "NSStoreModelVersionHashes"} = <CFBasicHash 0x6b723a0 [0x1ed8b48]>{type = immutable dict, count = 4,
entries =>
0 : <CFString 0x6b72240 [0x1ed8b48]>{contents = "Content"} = <CFData 0x6b72350 [0x1ed8b48]>{length = 32, capacity = 32, bytes = 0x8e9cd0ae096564b4a809b320b403172a ... 414356e3574f9d65}
3 : <CFString 0x6b71b50 [0x1ed8b48]>{contents = "Result"} = <CFData 0x6b722b0 [0x1ed8b48]>{length = 32, capacity = 32, bytes = 0x269fc7c2219f67a0999c5b6ba5e4358a ... d1d5608e4862793a}
5 : <CFString 0x6b72230 [0x1ed8b48]>{contents = "Amount"} = <CFData 0x6b72300 [0x1ed8b48]>{length = 32, capacity = 32, bytes = 0x24a58d8fb0e99f8b175fb3a9c58343f4 ... c0861d5accd4f241}
6 : <CFString 0x6b72210 [0x1ed8b48]>{contents = "Setting"} = <CFData 0x6b72260 [0x1ed8b48]>{length = 32, capacity = 32, bytes = 0xe464427daef6937e3a9cdf48cafba8e5 ... 91729a25ffbe4090}
}
7 : <CFString 0x1165ad8 [0x1ed8b48]>{contents = "NSStoreUUID"} = <CFString 0x6b71f50 [0x1ed8b48]>{contents = "C0CADBB5-E31C-46AD-893B-0B14D99259E5"}
8 : <CFString 0x1165978 [0x1ed8b48]>{contents = "NSStoreType"} = <CFString 0x1165988 [0x1ed8b48]>{contents = "SQLite"}
9 : <CFString 0x6b721a0 [0x1ed8b48]>{contents = "_NSAutoVacuumLevel"} = <CFString 0x6b72420 [0x1ed8b48]>{contents = "2"}
10 : <CFString 0x6b721c0 [0x1ed8b48]>{contents = "NSStoreModelVersionHashesVersion"} = <CFNumber 0x6b30d10 [0x1ed8b48]>{value = +3, type = kCFNumberSInt32Type}
}
, reason=Can't find model for source store}, {
URL = "file://localhost/Users/someone/Library/Application%20Support/iPhone%20Simulator/5.1/Applications/E7154F8A-8145-40CE-8ED3-FEF82D39055D/Documents/PocketMoneyManagement.sqlite";
metadata = {
NSPersistenceFrameworkVersion = 386;
NSStoreModelVersionHashes = {
Amount = <24a58d8f b0e99f8b 175fb3a9 c58343f4 9bf16d35 ad5e07ac c0861d5a ccd4f241>;
Content = <8e9cd0ae 096564b4 a809b320 b403172a 24218049 36a6fa77 414356e3 574f9d65>;
Result = <269fc7c2 219f67a0 999c5b6b a5e4358a 2da8d836 69806579 d1d5608e 4862793a>;
Setting = <e464427d aef6937e 3a9cdf48 cafba8e5 ddf8a144 0949e9fe 91729a25 ffbe4090>;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
""
);
NSStoreType = SQLite;
NSStoreUUID = "C0CADBB5-E31C-46AD-893B-0B14D99259E5";
"_NSAutoVacuumLevel" = 2;
};
reason = "Can't find model for source store";
}
たくさんのデバッグ情報が出ますが、「reason = "Can't find model for source store"」が気にする部分のようです。
このエラーが出た際に、iPhone実機やシミュレーター上に前のビルドから残っているアプリを消してから、 もう一度起動するという手も新規開発中ならOKですが、既にリリースしたアプリの場合には、 CoreDataのモデルのバージョン移行が必要になります。
CoreDataのバージョン移行を行う手順
CoreDataでテーブル定義を変更する方法は、そんなに難しくなく、以下の手順で実施可能です。なお今回は、既にver1とver2のCoreDataが存在する状態で、ver1→ver2, ver1→ver3にデータを移行する例となっております。
初めてver2を作る場合も、一緒なので参考になると嬉しいです。
新しいモデルを追加する
まずは、メニューバーで「Editor → Add Model Version」を選択します。続いて、モデル名を記入して、拡張するベースとなるモデルを選択します。
ここで記入するモデル名は、バージョン番号などを入れると良いかもしれません。
Finishボタンをクリックすると、以下のようにxcdatamodelIdの下に、作成したモデルバージョンが追加されます。
追加したモデルバージョンで変更したいようにテーブル定義を変更する
続いて、上記で追加したモデルバージョンに対して、今回変えたい内容を記載します。以下の例では、新しく「NewEntity」というテーブルを新規追加しています。
移行方法を記載したファイルを作成する
今回追加したバージョンにデータを移行する方法を記載したファイルを作成します。ここで重要なのは、ver1, ver2が既にある場合には、ver1→ver3、ver2→ver3というように、既存のバージョン全てから 今回追加したバージョンへ移行する為のファイルを記載する必要がある点です。
ユーザーによっては、アプリをUpdateしてなくて、ver1のデータモデルのアプリを使い続けてるかもしれないので。
移行定義を行うファイルは、「File > New > File...」から追加できます。
ファイル追加ダイアログを開いたら、CoreDataにあるMapping Modelを選択し、Nextボタンを押します。
次のページでは、移行元のバージョンを選択します。今回は、ver1を移行元バージョンとして選択しています。
次に、移行先のバージョンを選択します。今回は、ver3を移行先として選択します。
最後にファイル名を入力します。ファイル名は自由に設定出来ますが、ver1_to_ver2などの移行元と先が分かると便利かもしれません。
作成すると以下のような内容のファイルが作成されます。自動的に移行元と移行先が定義されますが、変更したい場合には 自分で変更することもできます。
データ移行を行う実装を行う
今まで作成したデータ移行用のファイルを、アプリ起動時に読み込んで実行してもらう為に、 ソースコードの記載を変更します。CoreDataを読み込む部分(XCODEプロジェクトで作るとApplicationDelegateを実装したクラスの中とか)で、 読み込む処理を変更します。
(変更前のソースコード)
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (persistentStoreCoordinator_ != nil) { return persistentStoreCoordinator_; } NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"PocketMoneyManagement.sqlite"]]; NSError *error = nil; persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return persistentStoreCoordinator_; }
上記のソースコードのうち、「addPersistentStoreWithType:configuration:URL:options:error:」メソッド呼び出しの部分で、 optionsを指定して、データ移行を行うように設定します。
変更後のソースコードは以下となります。
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (persistentStoreCoordinator_ != nil) { return persistentStoreCoordinator_; } NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"PocketMoneyManagement.sqlite"]]; NSError *error = nil; persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; /*********** 移行を行う為のoptionsを生成する ***********/ NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; /*********** options指定ありでCoreDataを読み込む **********/ if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return persistentStoreCoordinator_; }
NSDictionaryでoptionsを作成して、自動的にデータ移行を行うように設定しています。
参考資料
以下の資料を参考にしました。もっと詳しい情報は、以下を参照下さい。Core Data Versioning.pdf@Apple Developer
最後に
CoreDataのマイグレーションはもっと難しいかと思いましたが、手順さえ踏めば簡単なんだと 知ることが出来て良かったです。自身のアプリを進化させるべく、今後もiPhoneアプリ開発を頑張利隊と思います。
そこで得た情報をまたブログに記載させて頂きたいと思います。
いつもの2倍以上の記事量でしたが、最後までお読み頂きまして本当にありがとうございます。