今回は、CoreDataの手動マイグレーションをやってみようとしてハマったのでそのメモです。
最終的には自動マイグレーションで終わらせました。ある程度の手動マイグレーションの感覚は掴めた気がしていますが、なぜ最後まで動かなかったのかは未解決です・・。
##マイグレーション
まず、マイグレーションの手順。
-
CoreDateのモデルファイル(XXXX.xcdatamodeld)を選択した状態でメニューから「Editor > Add Model Version…」を選択し、新しいバージョンのモデルファイルを生成
-
新規作成した新しいバージョンのモデルデータを選択した状態で プロパティなど、Entityを編集
「QiitaModel 2.xcdatamodel」が新しく生成したモデルファイル -
生成時、移行元と移行先のモデルデータを選択する必要があるので、移行元→移行先の順でファイルを指定してマッピングファイルを作る
-
生成したマッピングファイルを選択し、Inspectorの「Custom Policy」の欄に、マイグレーションを処理するクラスを指定する
-
(5)で指定したクラスを新規作成する。継承元クラスは
NSEntityMigrationPolicy
クラス -
マイグレーションが発生した際、アプリケーションは自動でマイグレーション用のデリゲートメソッドを呼び出す
-
1レコードあたり1回ずつデリゲートメソッドが呼ばれ、その中で移行元のコンテキストから値を取り出し、移行先のデータに手動でコピーしていく。(新規のデータについてはデフォルト値があればそこも手動で)
-
すべての処理が終わったら、無事マイグレーション完了。
ただ、この「自動でマイグレーション用のデリゲートメソッドが呼ばれる、というところでなぜか呼ばれず、どうしても解決しなかったので自動マイグレーションで対応しました
###サンプルコード
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance entityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError **)error
{
NSManagedObjectContext *context = [manager destinationContext];
NSString *entityName [mapping destinationEntityName];
NSManagedObject *dInstance = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
[dInstance setValue:@"value1" forKey:@"anyKey1"];
[dInstance setValue:@"value2" forKey:@"anyKey2"];
//…
[dInstance setValue:[sInstance valueForKey:@"existsValue1" forKey:@"existsKey1"];
[dInstance setValue:[sInstance valueForKey:@"existsValue2" forKey:@"existsKey2"];
//…
return YES;
}
ざっくりやっていることを言うと、前のデータにあるやつはそれを取り出し新しいデータに移行し、ないのはデフォルト値なりを設定する、というもの。手動コピーみたいな感じ。
シンプルな場合はそれだけだけど、Relationとかが絡んでくると色々と大変になるはず。
見てもらうと分かるけど、ひとつのNSManagedObject
のみ処理している。つまり、管理しているデータ件数分、このメソッドが呼ばれるということ。なので、あまりに重い場合は処理を分けるなどして起動時のタイムアウトに引っかからないようにする必要があるみたい。
###自動マイグレーションで対応
大掛かりな変更でなければ、おそらくさくっとできる自動マイグレーションで十分。
やり方は、NSPersistentStoreCoordinator
をインスタンス化する際にオプションを指定するだけ。
//自動マイグレーション用にオプションを指定
NSDictionary *options = @{NSInferMappingAutomaticallyOption:@YES,
NSMigratePersistentStoresAutomaticallyOption:@YES};
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
//Error!
}
###現在のModel Versionを指定する
新しくモデルを定義しても、定義の参照先を変更しないと使えません。
バージョンの変更は、CoreDataのモデルデータを選択し、InspectorからModel Version
を変更したいものに変更する必要があります。
###マイグレーション必要かどうか判断する
マイグレーションが必要かどうかはNSPersistentStoreCoordinator
とNSManagedObjectContext
を使うことでチェックできる。
//meta dataを取得する
NSDictionary *sourceMetaData = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:storeURL error:&error];
//コンテキストに適合性を問い合わせる
BOOL isCompatible = [_managedObjectContext isConfiguration:nil compatibleWithStoreMetadata:sourceMetaData];
if (!isCompatible) {
//マイグレーションが必要
}
##ハマったこと
マイグレーションをやってみようと思ってごにょごにょしてるときに、とりあえず元に戻そうと差分をgitから戻したあと、Compilation failed for data model at path '/path/to/new/model.mom'
みたいなエラーでビルドできなくなった。
ファイルとかは元に戻したのになぜ・・と思っていて、↓のところの矢印部分をクリックしたら、そこに新規で作ったファイルがまだ残っていた。それのせいでエラーになっていた模様。それを消したら無事ビルドできた。
##参考にした記事