Help us understand the problem. What is going on with this article?

nilとNSNullの違いとNSNullをnilのように振る舞うようにする

More than 5 years have passed since last update.

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away