JSONパース等で発生するNSDictionary中のNSNull対応
REST API等の結果をJSONパース等でNSDictionaryに格納した時に
値が無いケースだとnilではなくNSNullオブジェクトがセットされる。
(NSDictionaryにはオブジェクトしかセット出来ないため)
この結果をアプリケーション側で利用する時にNSDictionaryが入れ子に
なっている場合以下の様なコードはエラーとなる。
[[hoge objectForKey:@"key1"]moge objectForKey:@"key2"]
これはhogeをNSDictionaryと想定しているが値がないためNSNullが設定されており
NSNullに対してobjectForKey:が送られ、そのようなメソッドが無いために発生する。
アプリケーション中で毎回nullチェックをするのがつらい場合は以下のように
forwardInvocation:メソッドをオーバーライドする事で回避出来る。
forwardInvocation:をオーバーライドする場合にはmethodSignatureForSelector:も
実装する必要がある。
これはNSInvocationだけではメソッドシグネチャ(戻り値や引数の型)が判断出来ないため、
アプリ側で設定する必要があるためと思われる。
処理の概要としては
1.あるインスタンスに対して存在しないメッセージが送信される。
2.メソッドが存在しない場合、まずmethodSignatureForSelector:が呼ばれる。
3.つづいてforwardInvocation:が呼ばれ、適当な処理を行う。
以下、サンプルコード
NSNull+IgnoreMessage.h
#import <Foundation/Foundation.h> @interface NSNull (IgnoreMessage) - (void)forwardInvocation:(NSInvocation *)anInvocation; - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector; @end
NSNull+IgnoreMessage.m
#import "NSNull+IgnoreMessage.h" @implementation NSNull (IgnoreMessage) - (void)forwardInvocation:(NSInvocation *)anInvocation { if ([self respondsToSelector:[anInvocation selector]]) { [anInvocation invokeWithTarget:self]; } } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *signature = [[NSNull class] instanceMethodSignatureForSelector:aSelector]; if(signature == nil) { //signatureはnilを返すとエラーになるので適当な値を設定して返す signature = [NSMethodSignature signatureWithObjCTypes:"@@:"]; } return signature; } @end
main.m
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSMutableDictionary *dic1 = [NSMutableDictionary dictionary]; [dic1 setObject:@"test1" forKey:@"key1"]; [dic1 setObject:[NSNumber numberWithInt:2] forKey:@"key2"]; // child dictionary // NSArray *vals = [NSArray arrayWithObjects:@"c1",@"c2", nil]; // NSArray *keys = [NSArray arrayWithObjects:@"ckey1",@"ckey2", nil]; // NSMutableDictionary *dic2 = [NSMutableDictionary dictionaryWithObjects:vals forKeys:keys]; [dic1 setObject:[NSNull null] forKey:@"key3"]; // nsnullエラーテスト // output NSLog(@"key1 = %@\n",[dic1 objectForKey:@"key1"]); NSLog(@"key2 = %@\n",[dic1 objectForKey:@"key2"]); NSLog(@"key3 = %@\n",[dic1 objectForKey:@"key3"]); NSLog(@"key3ckey1 = %@\n",[[dic1 objectForKey:@"key3"]objectForKey:@"ckey1"]); NSLog(@"key3ckey2 = %@\n",[[dic1 objectForKey:@"key3"]objectForKey:@"ckey2"]); } return 0; }
NSNullクラスに対してcategoryを作成する等して上記2メソッドを実装する。
methodSignatureForSelectorに関してはnil以外の値をNSMethodSignatureにセットして
返却すれば良い(実際この値は使用されないのでダミーとなる)
そして、forwardInvocation:のなかでrespondsToSelector:でメソッドが存在しない場合は
空振り(何もしない)ようにしておく。
こうする事でエラーになるのを回避出来る。
ちなみに、methodSignatureに設定しているC文字列の"@@:"は以下の様なルールで記述する
最初の@は戻り値の型を表している。
次は暗黙の引数が二つ。
一つ目はメソッドを実行するオブジェクト、もう一つはメソッドのセレクタとなる。
つまり上記の"@@:"は何らかのオブジェクトを返す引数無しのメソッドと言う事になる。
今回は空振りさせる事が目的なので何でも良かったが、何らかの処理を振り分ける様な
用途で使用する場合には、ここで設定したシグネチャと呼び出すメソッドがずれていると
エラーになるので注意が必要。
以下、型エンコーディングの一覧
C char
s short
i Int
l long
q long long
f float
d double
C unsigned char
S unsigned short
I unsigned int
L unsigned long
Q unsigned long long
V void
* C 文字列(char *)
@ オブジ工クト
# クラスオブジェクト
: セレクタ
[ 型] 配列
{ 名前= 型. •• } 構造体
^型 型へのポインタ
? 不明な型や要素