CoreData - マイグレーションを考慮した CoreDataManager パターン

2011年2月26日土曜日 | Published in | 2 コメント

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

2/27 マイグレーションにかかる時間を訂正・加筆しました(別の数字を誤って掲載してました。実際はもっと遅い)。

一般的な CoreDataManager パターンの問題


CoreData を使う場合 CoreDataManager というシングルトンを使って NSManagedObjectContext などを管理させるのが一般的なパターン。こんな感じ。
@interface CoreDataManager : NSObject {

 // Core Data Stack
 NSPersistentStoreCoordinator *persistentStoreCoordinator_;
  NSManagedObjectModel *managedObjectModel_;
  NSManagedObjectContext *managedObjectContext_;
}
@property (nonatomic,retain, readonly) NSPersistentStoreCoordinator* persistentStoreCoordinator;
@property (nonatomic,retain, readonly) NSManagedObjectModel* managedObjectModel;
@property (nonatomic,retain, readonly) NSManagedObjectContext* managedObjectContext;

+ (CoreDataManager*)sharedManager;
利用側のコードでは必要な時にここから NSManagedObjectContext を取り出せば良い。
NSManagedObjectContext* moc = [[CoreDataManager sharedManager] managedObjectContext];

ただこれだとエンティティに変更を入れてマイグレーションが必要になった時に困ることがある。例えば起動直後の画面で CoreData へアクセスするような構成で起動時にマイグレーションが発生した場合、起動ルール(20秒)にかかりアプリが起動に失敗するというケースがある。

Cocoaの日々: [iOS] 起動に時間がかかりすぎるとクラッシュする(原因と対策など)

CoreData のマイグレーションは非常に遅くて下図ぐらいのエンティティ構成でレコードが 1,200件程度(親テーブル 100件、子テーブル 12件)のデータの場合、40〜50秒 2分かかる(iOS 4.2.1/3GS、自動マイグレーション、変更はカラム2個追加)。

2/26追記:マイグレーションにかかる時間(上記と同条件)
240件  28秒
600件  1分
1,200件 2分
2,400  2分20秒
4,800  9分
※ケーブルで MacBookに接続した iPhone 3GS に Xcode経由で実行(デバッガ未使用)。


CoreData を使うアプリであればこの程度の件数はすぐに行くので、起動時にマイグレーションが走ると確実に落ちてしまう。これを防ぐためには起動時に CoreData へアクセスさせないのが最低限の対策になるが、その場合でもユーザが CoreData へアクセスする操作を行った瞬間にマイグレーション処理に時間がかかって画面が固まったようになるのでユーザビリティは良くない。


マイグレーションを考慮したパターン


よって CoreDataを使うアプリではマイグレーション用の画面を用意するのがベスト。処理フローはこんな感じ。
起動
 ↓
(1)マイグレーションチェック
 もし必要なら、マイグレーション用の画面へ遷移し、(2)マイグレーション実行
 ↓
通常画面
マイグレーションチェックは NSPersistentCoordinator を使えばわかる。

Cocoaの日々: [iOS][Mac] CoreData - マイグレーションが必要かどうかを知る

以下は実際に上記パターンを適用した時の画面例。
アプリ起動後にマイグレーションが必要と判断したら専用の画面をモーダル表示させる。
if ([manager isRequiredMigration]) {
   // do migration
   MigrationViewController* viewController = [[MigrationViewController alloc] init];
   viewController.rootViewController = self;
   viewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
   [self presentModalViewController:viewController animated:NO];
   [viewController release];
 :
表示した画面の中でマイグレーションを実行する。画面には UIActivityIndicatorView を表示して回しておく。iOS 4 以降であれば GCD が使えるのでこの辺りの処理は簡単に書ける。
- (void)viewWillAppear:(BOOL)animated
{
 dispatch_queue_t queue =
  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

 self.okButton.hidden = YES;
 self.buttonLabel.hidden = YES;
 self.indicator.hidden = NO;
 self.warningLabel.hidden = NO;
 
 dispatch_async(queue, ^{
  [[CoreDataManager sharedManager] doMigration];

  dispatch_async(dispatch_get_main_queue(), ^{
   self.message.text = @"バージョンアップが完了しました";
   self.indicator.hidden = YES;
   self.okButton.hidden = NO;
   self.buttonLabel.hidden = NO;
   self.warningLabel.hidden = YES;
  });
 });

}
マイグレーションが終わったらOKボタンを表示する。ここはアプリによってはそのまま通常画面へ遷移しても良い。


上記のように CoreDataManager にはマイグレーション用のメソッドが必要。こんな感じ。
@interface CoreDataManager : NSObject {

 // Core Data Stack
 NSPersistentStoreCoordinator *persistentStoreCoordinator_;
  NSManagedObjectModel *managedObjectModel_;
  NSManagedObjectContext *managedObjectContext_;
}
@property (nonatomic,retain, readonly) NSPersistentStoreCoordinator* persistentStoreCoordinator;
@property (nonatomic,retain, readonly) NSManagedObjectModel* managedObjectModel;
@property (nonatomic,retain, readonly) NSManagedObjectContext* managedObjectContext;

+ (CoreDataManager*)sharedManager;

// for migration
- (BOOL)isRequiredMigration;
- (BOOL)doMigration;
自動マイグレーションを使っている場合、マイグレーションが発生するタイミングは NSpersistentCoordinator にマイグレーションオプションを設定した上で -addPersistentStoreWithType:configuration:URL:options:error: を投げた時になる。以下は一般的な -psersistentStoreCoordinator のコード例。
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
 if (persistentStoreCoordinator_ != nil) {
        return persistentStoreCoordinator_;
    }

    persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc]
          initWithManagedObjectModel:self.managedObjectModel];
 
 NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
        [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
        [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
  
    NSError *error = nil;
 NSURL* fileURL = [CoreDataManager fileURL_]; 
    if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType
              configuration:nil
               URL:fileURL
              options:options
                error:&error]) {
  NSLog(@"Creating persistentStoreCoordinator was failed (%@, %@)", error, [error userInfo]);
  abort();
    }

    return persistentStoreCoordinator_;
}
NSMigratePersistentStoresAutomaticallyOption と NSInferMappingModelAutomaticallyOption がマイグレーションオプション。通常の -persistentStoreCoordinator メソッドではこのオプションを設定せず、別途用意する -doMigration でマイグレーション専用の NSPersistentCoordinator を用意しそこでオプションを設定してマイグレーションを実行(-addPersistentStoreWithType:configuration:URL:options:error:)すれば良い。マイグレーション実行後はここで作った NSPersistentCoordinator は捨てて良い(NSPersistentCoordinator は同じDBファイルに対して複数作成可能)。

なお CoreData へのアクセスタイミングをきちんと制御できるのであれば別途 -doMigration を用意せず既存のメソッドだけでも良い( [[CoreDataManager sharedManager] managedObjectContext] が最初に呼ばれたタイミングで -psersistentStoreCoordinator も呼ばれるのでそこでマイグレーションが走る)。

- - - -
CoreData を扱う上での一番のネックはマイグレーションに時間がかかること。たった一つのカラム追加でも現状は全データの移行作業が発生するので(特に iOSでは)この時間が馬鹿にならない。CoreDataはプログラミング的には非常に便利なのだがこの運用時のマイグレーション処理が少々厄介。アプリで CoreData を採用するかどうか判断する時にはこの点を考慮した方が良いかと思う。


参考情報


Cocoaの日々: [iOS] CoreData - マイグレーション[5] マイグレーション中にアプリを終了させたらどうなる?

Responses

  1. yoo
    2011年7月24日 3:22

    アプリのクラッシュログをみてここにたどり着きました。
    CoreData のマイグレーションも正しいし、開発段階では何度やってもうまくいっくので原因不明でしたが、アプリ起動時間でこけるんですね。。。
    これは参考になりました!

  2. yoo
    2011年7月24日 3:22

    アプリのクラッシュログをみてここにたどり着きました。
    CoreData のマイグレーションも正しいし、開発段階では何度やってもうまくいっくので原因不明でしたが、アプリ起動時間でこけるんですね。。。
    これは参考になりました!

  3. xcatsan says:
    2011年8月1日 13:30

    yooさん、こんにちは。
    開発時には出ないのに、実機では出るって問題が一番厄介ですよね。
    では。

  4. xcatsan says:
    2011年8月1日 13:30

    yooさん、こんにちは。
    開発時には出ないのに、実機では出るって問題が一番厄介ですよね。
    では。

Leave a Response

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