[iOS][Mac] CoreData - マイグレーション[1] NSEntityMigrationPolicy を使う

2010年12月6日月曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

以前、CoreData のマイグレーションについて調べたことがある。
(旧) Cocoaの日々: CoreData - マイグレーション

これはマッピングモデルを定義するだけで簡単にできる、いわゆる「自動マイグレーション」を解説していた。
今回は NSEntityMigrationPolicy を使ったいわゆる「手動マイグレーション」について調べた。


マイグレーションの概要


Core Data ではエンティティ(テーブル)の定義を変更すると必ずマイグレーションを行う必要がある。例えば現在のアプリケーションをバージョンアップする際に属性 email2 を一つ追加する場合、マイグレーション設定を行わないと Core Data 利用時に例外が発生してアプリケーションが落ちてしまう。
(例) CoreDataMigSample[8394:207] Unresolved error
 Error Domain=NSCocoaErrorDomain Code=134100 "The operation couldn’t be completed. (Cocoa error 134100.)"
 UserInfo=0x581be80 {metadata={type = immutable dict, count = 7,
    :
}
, reason=The model used to open the store is incompatible with the one used to create the store}, {
    metadata =     {
    :

マイグレーションを行うには2つの方法がある。

 1. Lightweight Migration(いわゆる自動マイグレーション)
 2. NSEntityMigrationPolicy利用(いわゆる手動マイグレーション)



1. の方法は Xcode でマッピングモデルを用意するだけでマイグレーションの設定ができる。属性の追加など簡易なものであればこの方法で最も簡単にマイグレーションを実現できる。
(詳しくは「(旧) Cocoaの日々: CoreData - マイグレーション」まで)

2. の方法は NSEntityMigrationPolicy のサブクラスを作成してマイグレーションに必要な実装をコーディングする。今回紹介するのは 2. の方法。


NSEntityMigrationPolicy を利用したマイグレーション


マイグレーションプロセスは "Three-Stage Migration" と呼ばれる。Core Data 利用時にバージョンの差異が検出されるとこのマイグレーションプロセスが実行される。
1. Creation Stage(属性のマイグレーション)
2. Relationship Creation Stage(関連のマイグレーション)
3. Validation Stage(検証ステージ)

NSEntityMigrationPolicy のサブクラスを用意して設定しておくと、このマイグレーションプロセスの中で NSEntityMigrationPolicy の各メソッドが呼び出されていく。サブクラスで必要に応じてこれらのメソッドをオーバーライドしてマイグレーションコードを記述する。

NSEntityMigrationPolicy Class Reference

メソッドの呼び出し順序は次の通り。
[1] – beginEntityMapping:manager:error: 

== Creation Stage ==
[2] – createDestinationInstancesForSourceInstance:entityMapping:manager:error:
    ⇒ インスタンス(NSManagedObject)の数だけ繰り返し呼び出される
[3] – endInstanceCreationForEntityMapping:manager:error:

== Relationship creation stage ==
[4] – createRelationshipsForDestinationInstance:entityMapping:manager:error:
   ⇒ インスタンス(NSManagedObject)の数だけ繰り返し呼び出される
[5] – endRelationshipCreationForEntityMapping:manager:error:

== Validation stage ==
[6] – performCustomValidationForEntityMapping:manager:error:
==
[7] – endEntityMapping:manager:error:
マイグレーション対象のエンティティ毎に NSEntityMigrationPolicy のサブクラスを定義し、マイグレーションプロセスに適した実装を行って行くのがこの方法のスタイルとなる。

なおマイグレーションプロセス自体をカスタマイズすることができる。詳しくは下記を参考のこと。
Core Data Model Versioning and Data Migration Programming Guide: Customizing the Migration Process


サンプル


実際に NSEntityMigrationPolicy を使ったマイグレーションのサンプルプログラムを作ってみる。

まず Xcodeのテンプレートから "Navigation-based Application" を選びオプション "Use Core Data for storage" を付けて新規プロジェクトを作成する。ビルドすると[+]ボタンを押した時のタイムスタンプを Core Data へ格納してテーブル表示するアプリが動く。
エンティティはこんな感じ。
このエンティティへカラムを追加する。day, memo, month を追加してみよう。
day と month は timeStamp の月と日の値(整数)を設定する。

まず新しいバージョンのモデルを作成する。現在のモデルファイル(*.xcdatamodel)を選択してメニュー「設計」→「データモデル」→「モデルバージョンを追加」を選ぶ。すると新しいバージョンのモデルファイルが作成する。
このファイルを開き、先ほどのカラムを追加する。

次にマッピングモデルファイルを作成する。新規ファイル作成で "Mapping Model" を選ぶ。
新旧のモデルファイルを指定すると、マッピングモデルファイルが生成される。ここでは 1to2.xcmappingmodel という名前にしてみた。マッピングモデルファイルを開くと、エンティティ毎のマッピング設定が表示される。
カスタムポリシーに NSEntityMigrationPolicy のサブクラス名を設定する。今回は EventEntityMigrationPolicy とした。

次に EventEntityMigrationPolicy クラスを作る。

@interface EventEntityMigrationPolicy : NSEntityMigrationPolicy {

}@end

@implementation EventEntityMigrationPolicy

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance
entityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError **)error
{
 NSManagedObjectContext* context = [manager destinationContext];
 NSString *entityName = [mapping destinationEntityName];
 
 NSDate *timeStamp = [sInstance valueForKey:@"timeStamp"];
 
 NSCalendar* calendar = [[[NSCalendar alloc]
             initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
 NSDateComponents* components = [calendar components:NSMonthCalendarUnit|
             NSDayCalendarUnit fromDate:timeStamp];

 NSManagedObject* dInstance = [NSEntityDescription
             insertNewObjectForEntityForName:entityName inManagedObjectContext:context];

 [dInstance setValue:timeStamp forKey:@"timeStamp"];
 [dInstance setValue:[NSNumber numberWithInt:[components month]] forKey:@"month"];
 [dInstance setValue:[NSNumber numberWithInt:[components day]] forKey:@"day"];
 [dInstance setValue:@"Good morning!" forKey:@"memo"];
 
 return YES;
}
@end
sInstance にマイグレーション元のインスタンスが渡されるのでここから day と month を抽出してマイグレーション先のインスタンスへ設定する。

ポイントは次の通り。

1. 移行先のインスタンス(NSManagedObject)を生成する必要がある。
 Core Data 側でお膳立てはしてくれない。

2. すべての属性について移行の処理を実装する。
 移行処理を書かない属性は移行されない(当たり前だが)。
 (この処理はモデル情報を利用すれば定型化できる)

3. マッピングモデルで定義された属性のマッピングは参照されない。

マイグレーションプロセスは Core Data 側が管理するが、個々の具体的なマイグレーション処理は開発者が責任を持って全属性について実装する必要がある。



ソースコード


GitHub からどうぞ。
xcatsan/iOS-Sample-Code at 2010-12-06 - GitHub


備考


マイグレーションは1件毎にメソッドが呼び出されて処理される。この為、件数が多い場合は時間・メモリの面でパフォーマンスが問題になるケースが出てくる。この場合は標準のマイグレーションプロセスをカスタマイズして最適化することが可能。この方法については下記に解説がある。
Multiple Passes—Dealing With Large Datasets



参考情報


Core Data Model Versioning and Data Migration Programming Guide: Introduction to Core Data Model Versioning and Data Migration Programming Guide
公式リファレンス。基本的な情報はここでほとんど網羅されている。

Core Dataのマイグレーション(手動編) | hippos-lab::blog
NSEntityMigrationPolicy を使い単一テーブルから複数テーブルへ変換するリレーションシップを持つケースのマイグレーションの解説。わかりやすい。

Responses

Leave a Response

人気の投稿(過去 30日間)