ページ

2010年2月3日水曜日

KVC - key の存在チェック

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

KVC (Key Value Coding) は便利だが、あるオブジェクトが指定の keyを持っていることを確認するにはどうすればいいのか?

例えば Book というクラスがある時
Book.h

@interface Book : NSObject {

 NSString* author;
 NSString* title;
}
@property (retain) NSString* author;
@property (retain) NSString* title;

@end

実行時に Book が "author" というkeyを持つかどうか知りたい。あるいは "weight" を持っているのかどうか。

NSKeyValueCoding Protocol にはそれらしいメソッドが見当たらなかった。
Mac Dev Center: NSKeyValueCoding Protocol Reference

Mac Dev Center のドキュメントを探していくと keyが指定された時の探索方法の解説があった。
Mac Dev Center: Key-Value Coding Programming Guide: Accessor Search Implementation Details

これによると例えば setValue:ForKey: をあるオブジェクトへ投げたときに、当該keyが存在しない場合は setValue:forUndefinedKey: が投げられるとある。
4. If no appropriate accessor or instance variable is found, setValue:forUndefinedKey: is invoked for the receiver.
この setValue:forUndefinedKey: を見るとデフォルトでは例外(NSUndefinedKeyException)が発生するとのこと。
Mac Dev Center: NSKeyValueCoding Protocol Reference

Discussion
Subclasses can override this method to handle the request in some other way. The default implementation raises an NSUndefinedKeyException.

ネットで調べてみると Stack Overflow にこの内容の質問が出ていた。
How do you tell if a key exists for an object using Key-Value Coding? - Stack Overflow

提示されていた方法をまとめると次の通り。

  1. 例外をキャッチして処理を続行 @try-@catch
  2. valueForUndefinedKey: を実装(セッターの場合は setValue:forUndefinedKey:)
  3. class_copyPropertyList( ) でプロパティリストを取得してチェック
  4. respondsToSelector: でアクセッサ実装の有無をチェック
なるほど。

コードを書いて挙動を確認してみた。先程の Book クラスに対して試してみる。


Book* book = [[Book alloc] init];


[book setValue:@"APPLE" forKey:@"author"];
[book setValue:@"iPad book" forKey:@"title"];


ここまではいい。


[book setValue:@"a value" forKey:@"test"];


"test" は存在しないので例外が発生。

[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key test.



ちなみにオブジェクトが NSManagedObject のサブクラスの場合でも上記例外が発生した(通常のクラスと変わらない)。



undfined 系メソッドを実装する。

@implementation Book

@synthesize author;
@synthesize title;

- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
// do nothing
}

- (id)valueForUndefinedKey:(NSString *)key
{
return nil;
}
@end


すると例外が消えた。


次に respondsToSelector:を試す(undefined 系メソッドを一旦コメントアウトしておく)。

if ([book respondsToSelector:NSSelectorFromString(@"test")]) {
NSLog(@"ok: test");
[book setValue:@"a value" forKey:@"test"];
}

if ([book respondsToSelector:NSSelectorFromString(@"author")]) {
NSLog(@"ok: author");
}


"ok: author" のみ表示された。

- - - -
どの方法を取るかは状況によりそうだ。

なお下記のように undefined系メソッドをカテゴリで実装しても有効に働いた。

@implementation Book (Addition)

- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
// do nothing
}

- (id)valueForUndefinedKey:(NSString *)key
{
return nil;
}

@end