日本語でCore Dataのマイグレーションについて書かれているブログは少ない。「Cocoaの日々: [iOS][Mac] CoreData - マイグレーション[1] NSEntityMigrationPolicy を使う」を参考にさせて頂きました。
一度、SQLiteのファイルを作った後に、xcdatamodelで属性を増やしたりと、スキーマーを変更してからアプリを起動させると、以下のようなエラーが発生して落ちてしまう。
2011-02-15 10:04:16.087 CoreDataMigrationTest[471:207] Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 "The operation couldn’t be completed. (Cocoa error 134100.)" UserInfo=0x5846b90 {metadata=<CFBasicHash 0x5846820 [0xff63e0]>{type = immutable dict, count = 6, entries =>
対策法としては、単純に起動させるのであればアプリを一度アンインストールしてから、インストールしなおせばアプリは起動してくれるが、既にユーザーに一旦配布した状態でアップデートする用途で使うには向きません。
ましてや商用で出してるアプリがアップデート出来ないとなると、それだけでAppStoreのコメント欄には恐ろしい文章が並びます。
バージョン間をまたがってデータモデルに変更がある場合でも落ちないように、NavigationControllerを使ったテンプレートプロジェクトを使って、Core Dataのマイグレーションについて調査してみましょう。
自動マイグレーションを行う
新規プロジェクトから「Navigation-based Application」を選択する。Use Core Data for strorageのチェックは必ず付けておくようにしましょう。
プロジェクト名は適当に、CoreDataMigrationTest という名前にしました。プロジェクトを作ったばかりの状態のものを旧バージョンと呼ぶ事にします。
「Navigation-based Application」でプロジェクトを作ると、「+」ボタンをタップする度にCore Dataを作成し現在時刻を詰め込んでくれます。旧バージョンで適当な回数だけ「+」ボタンをタップして、旧バージョンのデータベースファイルを作成しておきましょう。
これで、CoreDataMigrationTest.xcdatamodelに属性を追加して、アプリを実行すると一番最初に書いたUnresolved error Errorが発生してしまいます。
新バージョン用のデータモデルの準備
グループとファイルの一覧上で、CoreDataMigrationTest.xcdatamodeld を選択した状態で、ツールバーから[設計]→[データモデル]→[モデルバージョンを追加]を選択します。
「CoreDataMigrationTest.xcdatamodeld」に「CoreDataMigrationTest 2.xcdatamodel」が追加されていれば問題ありません。ここで適当な属性を追加してみましょう。Data型の「newAttribute」を追加しました。
「CoreDataMigrationTest 2.xcdatamodel」を選択し、ツールバーから[設計]→[データモデル]→[現在のバージョンを設定]を選択します。
新旧間のデータのマッピングモデルの準備
「CoreDataMigrationTest.xcdatamodeld」を右クリックして、[追加]→[新規ファイル]を選択して、Mapping Modelを作成します。
名前は判りやすいように、ver01to02.xcmappingmodelとしました。
マイグレーションを行う為にソースモデルを設定します。今回は旧バージョンから新バージョンへマイグレーションを行いたいので、ソースモデルには「CoreDataMigrationTest.xcdatamodel」を、デスティネーションモデルには「CoreDataMigrationTest 2.xcdatamodel」を設定します。
ver01to02.xcmappingmodelをダブルクリックで開いて、どの属性同士をマッピングさせるのかを定義します。試してはいませんがある程度だったらマッピングモデルを作成しなくても自動マイグレーションはしてくれるそうです。
自動マッピングさせるようにCore DataとSQLiteの関連付け時にオプションを指定する
NSPersistentStoreCoordinatorのオブジェクトを取得する為に、persistentStoreCoordinatorのアクセサを使用します。これは、Core DataのデータモデルとSQLiteの実体を関連付ける役割を持ちます。
データモデルと実際のSQLiteの定義が異なる場合に、自動マイグレーションを行うオプションを有効にします。
/** Returns the persistent store coordinator for the application. If the coordinator doesn't already exist, it is created and the application's store added to it. */ - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (persistentStoreCoordinator_ != nil) { return persistentStoreCoordinator_; } NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataMigrationTest.sqlite"]; // データモデルと実際のSQLiteの定義が異なる場合に、 // 自動マイグレーションを行うオプションを有効に NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; NSError *error = nil; persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return persistentStoreCoordinator_; }
これで旧バージョンのSQLiteファイルを入れた状態で、新バージョンのアプリを立ち上げたとしてもデータモデルのスキーマとSQLite実体のスキーマの定義の違いがあれば、自動的にマイグレーションが行われて、新しい定義のSQLiteとして扱われる事になります。