CoreData - 既存のモデルに項目を追加するのは大変

CoreDataプログラミングでは、ビジュアルツールでモデルを作成しそれをインタフェースにドロップするというお気楽プログラミングが可能ですが、モデルに項目を追加する場合ちょっと大変です。あるエンティティにたった一つプロパティを追加したかっただけなんですが...

モデル変更前と後で作成したドキュメントに互換性がなくても構わないのであれば、エンティティにプロパティを追加・インタフェースビルダーで対応するプロパティ用のUIを作成してバインドしてあげるだけなんですが、旧モデルで作成した書類も読み込めるようにするにはひと工夫必要です。単純にプロパティを追加しただけでは、当然のことながら旧モデルベースで作成された書類をロードすると項目にズレが生じます。readFromURLあたりでこれを回避する処理を入れればなんとかなるかとも思ってトライしたのですが、そもそも、エンティティがどのようにストアされているかフレームワーク任せでプログラマである自分自身も知らない(これは恥ずかしいことですね :-<)わけで早々に断念。代わりに新しいエンティティを作成してリレーションを持たせることに。ここでは、既存のエンティティ(クラス)をkEntityとして説明します。

  • 追加モデル(モデルクラス=kNewEntity)を作成
  • 親(既存)のモデル(kEntity)と関連(1:1)付ける
  • 追加モデルのプロパティに対応するUI項目をIBで作成しバインド

当然、旧モデルベースのドキュメントは関連付けた新しいオブジェクトをもっていませんから関連はオプショナルとして定義、既存のkEntityのNSManagedObjectクラスに新しいエンティティオブジェクトへのアクセッサ(kNewEntity/setKNewEntity)も必要になります。また、IBでのモデルキーパスは、"親の関連名.子のプロパティ名"の形式で指定します。

内心、これだけで後はうまくやってくれるかと期待したのですが甘くはなく、追加分のモデルオブジェクトの生成は自前で管理する必要がありました。kNewEntityオブジェクトを作成するタイミングとしては、

  • 新規にkEntityオブジェクトを追加する時
  • 既存のkEntityオブジェクトをFetchした時

の2点。これを言葉どおり、awakeFromInsertとawakeFromFetchをオーバーライドします。

-(void) awakeFromInsert
{
  [super awakeFromInsert];
  [self setKNewEntity:
    [NSEntityDescription insertNewObjectForEntityForName:@"kNewEntity"
      inManagedObjectContext:[self managedObjectContext]]
  ];
}
-(void)awakeFromFetch
{
  [super awakeFromFetch];
  if ([self kNewEntity] == nil)
  {
    NSManagedObjectContext* moc = [self managedObjectContext];
    [[moc undoManager] disableUndoRegistration];
    kNewEntity*  kNew = [NSEntityDescription 
   insertNewObjectForEntityForName:@"kNewEntity" inManagedObjectContext:moc];
    [kNew setValue:self forKey:@"kEntity"];
    [self setKNewEntity:kNew];
    [moc processPendingChanges];
    [[moc undoManager] enableUndoRegistration];
  }
}

awakeFromFetchのほうは、そのままだと既存のドキュメントを開いただけでダーティになってしまうのでアンドゥマネジャーを一時オフにしています。あと、忘れがちなのは関連プロパティのセット。この場合、kEntity自身をセットしています。逆に、既存のドキュメントに、追加したエンティティオブジェクトのプロパティだけを編集して(この場合で言えば、kNewEntity.aだけを編集して)保存しても関連エンティティ(kEntity)オブジェクトがダーティでないので保存はしてくれません。kNewEntityのsetメッセージで

- (void)setA:(NSString *)value 
{
  [self willChangeValueForKey:@"a"];
  [self setPrimitiveValue:value forKey:@"a"];
  [self didChangeValueForKey:@"a"];
  [[self kEntity] setValue:self forKey:@"kNewEntity"];
}

のように無理矢理リセットしてダーティに。これでようやくsave時にkNewEntityオブジェクトも保存されるようになりました。

モデル変更といえば重大な仕様変更ですが、たった一つプロパティを追加するというためだけに手間がかかりすぎ。「保存はテキストで」とUNIXの哲学がありますが身に沁みますねぇ。CoreDataでは、binary/XML/SQLiteの保存形式が選べますがやっぱりXML/SQLiteにしておくべきだったかな?もし、このアプリケーションがXMLでデータを保存していたらもっと簡単だったんじゃないかなぁと、しみじみ思います。

添付サイズ
entity.gif9.19 KB

この記事のトラックバックURL:

http://hippos-lab.com/blog/trackback/215

返信