[Mac] NSArrayController に Undo/Redo を実装する [2] カスタムモデル

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

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

[前回] Cocoaの日々: [Mac] NSArrayController に Undo/Redo を実装する

モデルのクラスを NSMutableDictionary ではなくカスタムモデル(クラス)を使うケースを考えてみた。


カスタムモデル


こんなクラスを定義してみた。
@interface Book : NSObject {

 NSString* titile_;
 NSString* author_;
}
@property (nonatomic, copy) NSString* title;
@property (nonatomic, copy) NSString* author;

@end
NSMutableDictionary と同等の働きを持つが、値の管理をプロパティで行っている。NSArrayController にモデルのクラスを伝えるには -[NSObjectControlelr setObjectClass:]を使う。
NSObjectController Class Reference

Interface Builder を使えばインスペクタでクラス名を指定できる。


プロパティから監視キーを自動取得する


プロパティはランタイム関数 class_copyPropertyList を使うと特定のクラスで定義されているプロパティ名のリストを取得することができる。これは以前紹介したことがある。

[参照] (旧) Cocoaの日々: @property の一覧を取得する

これを使って KVO登録に必要なキー名の一覧を取得してみた。
- (NSArray*)_propertyListOf:(id)object
{
 NSMutableArray* list = [NSMutableArray array];
 unsigned int outCount, i;
 objc_property_t *properties = class_copyPropertyList([object class], &outCount);
 
 for(i = 0; i < outCount; i++) {
  objc_property_t property = properties[i];
  const char *propName = property_getName(property);
  NSString *propertyName = [NSString stringWithUTF8String:propName];
  [list addObject:propertyName];
 }
 free(properties);
 return list;
}
これを使い KVO登録を行う。
- (void)_addObserverFor:(NSArray*)objects
{
 for (id object in objects) {
  NSArray* keys = self.keys;
  if (!keys) {
   keys = [self _propertyListOf:object];
  }
  for (NSString* key in keys) {
   [object addObserver:self
      forKeyPath:key
      options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
      context:nil];   
  }
 }
}
結果はOK。前回のようにわざわざ監視キーの一覧を渡さなくて良いのでこちらの方が使い勝手が良い。自分の場合、サンプルを除けば NSMutableDictionary をモデルとして使うことはあまり無くて、どちらかといえば手間をかけてカスタムクラスを定義する方なのでこちらで十分使えそうだ。


備考


前回の初期化コードにはバグがあって Interface Builder で定義した内容が実行時に反映されていなかった。
- (id)initWithCoder:(NSCoder *)aDecoder
{
 // self = [super init];  ※間違い
 self = [super initWithCoder:aDecoder];
 if (self != nil) {
  [self _setup];
 }
 return self;
}
Nib 経由でインスタンス化される場合は initWithCoder: が呼ばれるが、そこで初期化処理を書く場合は親の initWithCoder: を呼ぶ必要がある。それによって Nibで定義した情報を元にインスタンス化されるのだが、前回のように initを読んでしまうと Nibとは無関係に普通に初期化してしまう。


ソースコード


GitHub からどうぞ。 ArrayControllerUndoSample at 2010-12-23 from xcatsan/MacOSX-Sample-Code - GitHub

Responses

Leave a Response

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