nilとNSNullの違い
プログラミング言語でnullは基本的には値がないことや変数が初期化されていないということを表す。
Objective-Cではnilはポインタが何も実体を指していないことを表現し、変数がnilのときオブジェクトを持っていない状態となる。
ただ、NSDictionaryやNSArrayはnilを格納することが出来ず、
空を格納していることを表現するためにNSNullを使う。
nilとNSNullの大きな違いとして、値がnilである変数に対してメッセージを送った場合はnilを返す。nilは(id)0として定義されている。これに対し、オブジェクトがNSNullの場合、NSNullオブジェクトに存在しないメッセージを送るとNSInvalidArgument Exceptionの例外を投げ、これをキャッチしなかった場合はクラッシュしてしまう。
nilへのメッセージの戻り値は次のコードで実験できる。
void main()
{
NSNumber *num = nil; //説明のため明示的に初期化しておく
NSLog(@"%@", num); //=> (null)
NSLog(@"%d", [num integerValue]); //=> 0
NSLog(@"%d", [num boolValue]); //=> 0
}
参考: Appleの"Objective-Cによるプログラミング"
https://developer.apple.com/jp/devcenter/ios/library/documentation/ProgrammingWithObjectiveC.pdf
NSNullを使うことになる具体例
具体例を上げると、
例えばqiitaの新規投稿一覧APIを呼び出した際、gistのURLがない投稿は下記のようになっている。
"gist_url":null,
先述のようにNSJSONSerializationでこれをパースするとNSDictionaryにはNSNullが格納される。
ちなみにNSLogなどで出力すると下記のようになる。
"gist_url" = "<null>";
APIの設計指針によると思うが、
例えばgist_urlのデータが存在しないようなデータの場合には、JSONにgist_urlの項目自体含めなければ、NSDictionaryでアクセスしてもその存在がないのでnilとして扱うことが出来る。
もし新規にAPIを設計する場合にはデータがない場合には項目自体を含めないことを検討するのもよいかもしれない。
NSNullのメソッド呼び出し時の振る舞いの変更について
NSNullのオブジェクトにメソッドを呼びだそうとした場合にnilと同じように無視する方法は下記のサイトの記事にあるようにやるのが良い。
「NSNull のインスタンスは nil として振舞ってくれると嬉しいなって」
http://d.hatena.ne.jp/KishikawaKatsumi/20110505/1304598102
記事中では下記2つのメソッドをNSNullのカテゴリとして実装している。
- (NSMethodSignature*)methodSignature:(SEL)selector;
- (void)forwardInvocation:(NSInvocation*)invocation;
@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
どういう仕組なのか?
これら処理の内容について詳しくは下記のコラムが参考になる
「ダイナミックObjective-C AspectCocoa (3) - フォワーディングとポージングの利用」
http://news.mynavi.jp/column/objc/045/index.html
NSNullがnilのようにメソッドを無視するためのフォワーディングは、そもそも他のオブジェクトに処理を頼む仕組みの様子。
forwardInvocation:では実際にメソッドを呼び出すターゲットを指定している。nullメソッドはrespondsToSelector:がYESを返す場合のみNSNullオブジェクト自身をターゲットとし、それ以外のメソッドの場合は何もしない。
methodSignature:はメソッドシグネチャのインスタンス化を行おうとし、それがnilの場合には文字列からダミーのシグネチャのインスタンスを返すようにしている様子。
signatureWithObjCTypes:の引数"@^v^c"の意味ははっきり分からないが、デバッガでanInvocationの値を出力すると次のようになった
return value: {@} 0x0
target: {^v} <78f67c01>
argument 1: {^c} 0x34d70a5
つまり戻り値、ターゲット、引数をシグネチャで適当に指定しているということだろうか?
Apple公式のリファレンスを参考にすると@と^v, ^cがなんとなくわかる
「Type Encodings」
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html