西海岸より

つらつらざつざつと

CoreDataの罠-マイグレーションマッピングファイルの選択条件

アプリのアップデート時に、マイグレーションが必要になって実装時にはまったところのメモです。マイグレーション時にマッピングファイルが適切に選択されないという現象に悩まされました。

マイグレーションとは

アプリのバージョンアップとともに、CoreDataでデータモデルが変更(Entityの追加など)する場合、ユーザが蓄積した旧モデルのデータファイル(sqlite)を更新する処理(マイグレーション)が必要になります。これをマイグレーションと呼び、バージョンアップしたアプリの初回起動後通常一回だけ呼ばれます。ユーザがアプリ上で蓄積したデータを扱うため、マイグレーションの設計はアプリをメンテしていく上で最も必要な技術要素の一つで、かつ初めてでは習得がなかなか難しい技術要素と思います。
マイグレーションの基本的なやり方は以下のサイトが参考になるかと。

マイグレーションのパターン

マイグレーションのパターンは大きく2パターン

LightWeightなマイグレーション

テーブル追加や、カラムの追加等、モデルの差分が小さい場合に、自動で移行してくれる方法。
以下のようにpersistentStoreCoordinatorを構築する処理時にオプションを指定しておくだけで、自動でマイグレーションしてくれる。

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
      [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
      [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
                           nil];
 
    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }   
Manualマイグレーション

自分でデータのマッピング定義ファイル(マッピングモデルファイル)を用意して、バージョンアップのパターンによってどのようにデータを移行するか、自分で処理を記述できる。例えば一つのEntityを二つに分割したり、また逆に統合したりする際には、この仕組みを使って独自にマッピングを定義し、ロジックが必要な場合は実装が必要になります。

今回困ったこと

前置きが長くなったけど、今回困った現象が起きたのはManualマイグレーションの方。失敗するパターンのプロジェクトをGithubにアップしてます。

https://github.com/mmasashi/MigrationNG

  • プロジェクトの構造


  • モデルのバージョンの違いは、保持するEntityの違いのみ
    • version 1 : Student School
    • version 2 : Student School Dummy2
    • version 3 : Student School Dummy3
    • version 4 : Student School Dummy4

このプロジェクトを実行すると、意図的にマイグレーションを発生させるため、version3で作成されたsqliteファイルをDocuments以下にコピーし、そのversion3のsqliteに対しversion4へマイグレーションします。

ここで問題は、version3からversion4へのモデルバージョンアップを想定して作成したマッピングファイルが実行されることが期待されるけど、version1からversion4の方が呼ばれてしまうところ。

  • コンソールには以下の文字列が。。
2011-11-03 19:04:27.367 MigrationNG[3654:207] -[MigrationPolicy1to4 beginEntityMapping:manager:error:] IN

なぜか

なぜこうなるか現状はまだよくわからず、推測している段階です。(ドキュメントも確認しましたが。。。)
ただ、以下の画像のようにXcodeマッピングモデルで指定したモデルバージョンが実行時には参照されていないことは確か。推測ですが、マッピングファイルに含まれるEntityのマッピング定義がすべて適応できるかどうかで、マッピングファイルを選択していると思われます。sqliteのEntityだけ見ると、version1->version4が問題なく適応できてしまいますし。。ちなみに、Mapping1to4を削除するとMapping3to4が選択され実行され、この現象からファイル昇順でマッピングファイルの検証&選択されるのかと。ただ、この点は仕様に明記されていないので信頼しない方がよく、ファイル名を変更するなどの対応は避けた方がよいです。


対策

対策として、すべてのマッピング定義ファイルで、カスタムポリシーに同じ実装ファイル(NSEntityCustomPolicyを継承したクラス)を参照するように修正し、その中でモデルバージョンを判定して処理を分岐するようにしました。例えば、今回のサンプルの場合Dummyテーブルの存在の有無によって判断が可能。(マッピング定義ファイルの本来の役割が果たせてないのが悲しい)

ただ、マッピングファイルがいずれのバージョン間も適用可能なケースってレアな気がしますので、想定とおりマッピングファイルが適応される場合はこの記事に関することは気にしなくてよいと思います。