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 anNSUndefinedKeyException
.
ネットで調べてみると Stack Overflow にこの内容の質問が出ていた。
How do you tell if a key exists for an object using Key-Value Coding? - Stack Overflow
提示されていた方法をまとめると次の通り。
- 例外をキャッチして処理を続行 @try-@catch
- valueForUndefinedKey: を実装(セッターの場合は setValue:forUndefinedKey:)
- class_copyPropertyList( ) でプロパティリストを取得してチェック
- 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