読者です 読者をやめる 読者になる 読者になる

24/7 twenty-four seven

iOS/OS X application programing topics.

NSNull のインスタンスは nil として振舞ってくれると嬉しいなって

iPhone Cocoa Objective-C


とか思ってたのですが、そう考える人はやっぱりほかにもいるようです。



たとえば私がいちばん面倒だなと思うのはjson-frameworkがnullをNSNullにマッピングするので(nilはNSArrayやNSDictionaryに格納できないため)でWeb APIからのレスポンスをParseする際に、値がセットされるときとnullがセットされるときと両方あるような場合、ハンドリングがとたんに面倒になるのですね。

 NSString *results = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
 NSDictionary *dic = [results JSONValue];
 NSString *shortURL = [[[dic objectForKey:@"results"] objectForKey:longURL] objectForKey:@"shortUrl"];

上記の場合、longURL の値が NSNull だったとすると objectForKey: の呼び出しが失敗してクラッシュします。
このとき NSNull が nil だとしたら、nil へのメッセージは単に無視されるので問題ないわけなのでそうなっていたら便利なのにという話です。


というわけで、下記のようなカテゴリをどこかに書いておくと、NSNull へのメッセージはすべて無視されるので、まるで nil のように扱えて便利です。
何をしているかというと、メッセージフォワーディングの仕組みが動作するように methodSignatureForSelector: および forwardInvocation: をオーバーライドします。
そうすると存在しないメソッドを呼びだそうとするとこれらのメソッドが呼ばれるので、forwardInvocation: メソッドで NSNull に存在しないメソッドの呼び出しは無視するように処理を変更しています。

[参考]Does Objective-C use short-circuit evaluation? - Stack Overflow

@implementation NSNull(IgnoreMessages)

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([self respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:self];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *sig=[[NSNull class] instanceMethodSignatureForSelector:aSelector];
    // Just return some meaningless signature
    if (sig == nil) {
        sig = [NSMethodSignature signatureWithObjCTypes:"@^v^c"];
    }
    
    return sig;
}

@end


東日本大震災緊急支援募金募集中。国際NGOワールド・ビジョン・ジャパン