nilにまつわるエトセトラ

  • 150
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

nilとは何か?

nilとNULLの由来

どちらも、「何もない」という意味

  • NULLの由来は、ラテン語で「無」を意味するnullus
  • nilの由来は、ラテン語で「無」を意味するnihil

Objective-Cにおける、nilとNilとNULL

いずれも「ポインタが何も指していない」ことを示し、実体は0です。
それぞれ、想定されている型が異なるため、コンテクストに応じて使い分けるべきです。

  • nil : (id)0 -> オブジェクトを何も指していない
  • Nil : (Class)0 -> クラスを何も指していない
  • NULL : void * -> ポインタを何も指していない
    id nullObj = nil;
    // 空じゃない場合に実行したい!!
    if(nullObj){
        //OK
    }
    if(nullObj != nil){
        //OK
    }
    if(nullObj != 0){
        //OKだけど、コードとして分かりにくいのでオススメしない
    }

NSNull

NSNullは「何もない」という情報をもったオブジェクトであり、本質的にnilとは異なります。
NSArray等のコレクションはnilを収納できないので、「空である」という状態を保持したい場合にはNSNullを使用します。

    id nullObj = [NSNull null];
    // 空じゃない場合に実行したい!!
    if(nullObj){
        //ダメ。オブジェクトとしては存在する
    }
    if(nullObj != nil){
        //ダメ。NSNullオブジェクトは、nilじゃない
    }
    if(![nullObj isEqual: [NSNull null]]){
        //OK NSObject的な比較なので問題ない
    }
    if(nullObj != [NSNull null]){
        //OK シングルトンなので、ポインタ比較でも問題ない
    }

NSNumberの0と、NSIntegerの0

NSNumberの0(@0)は、0ではなく、0という値を持つNSNumberのインスタンスです。
NSIntegerの0は、nilと同じ0です。

コーディングの中でのnil

nilに対するメソッド呼び出し結果はnilである

Objective-C的な記法は、このnil呼び出し仕様に強く依存しています。
たとえば、一番基本的なalloc + initという書き方も、allocが失敗してnilになっても、initが問題なく処理されることを前提としています。

[[NSObject alloc]init]

これはObjective-Cの特徴であり文化なので、これを生かしたコーディングをすることで、よりObjective-Cらしいコードになります。

if(self.label != nil){
  self.label.text = @"";
}
//nilチェックはシンプルにかける
if(self.label){
  self.label.text = @"";
}
//そもそもチェックしなくても良い
self.label.text = @"";

ただし、他の多くの言語(Java等)では、nil(に相当するNULL等)に対して呼び出しを行うとエラーになります。
Objective-Cが特別と思っておかないと、他の言語を使った時にNull Pointer Exceptionを見て涙することにになります。

nilに対するブロック呼び出しは落ちる

ブロックはNSObjectに近い扱いができますが、NSObjectではありません。ブロックがnilの場合、それに対する呼び出しを行うと落ちるので要注意です。
ブロックの安全な呼び出し方

安全 != 意図通り

nilに対する呼び出しは安全ですが、必ずしもプログラマの意図通りの動作をするとは限りません。
nilで落ちないからこそ、nilでも正しく動くコーディングスタイルが必要になります。

if(obj.state.isOK){
  //ここに入るのは、(obj != nil) && (obj.state != nil) && (obj.state.isOK == YES)
}
if(!obj.state.isOK){
  //ここに入るのは、(obj == nil) || (obj.state == nil) || (obj.state.isOK == NO)
}

ARCとnil

ARCとは何か

ARCはObjective Cで使用するメモリ管理の方式で、従来プログラマが明示的に行っていた参照数管理をコンパイラが自動的に行う物です。リファレンスカウンティングGCに近いですが、頑張るのがランタイムではなく、コンパイラな点が異なります。(詳しく知りたい人はぐぐるといいと思うよ)

Automatic Reference Coutingという名前のとおり、「参照数のカウント」を自動的に行い、参照数が0になったオブジェクトを自動的に解放してくれます。(このカウントアップをretain、カウントダウンをreleaseと呼びます)
この「参照している」という状態の反対が、「何も指していない」というnilです。

強参照(strong pointer)

普通に参照を定義すると、それは強参照となり対象をretainします。
ARCは強参照が一つでもある限りオブジェクトを解放しないので、不要なオブジェクトを指すポインタには明示的にnilを設定して解放する必要があります。実際には変数のスコープとオブジェクトグラフの構造により、あまり意識せずに解放は行われます。

弱参照(weak pointer)

__weakを指定して作成した参照は、弱参照となり対象をretainしません。
ARCは強参照が0になると弱参照があってもオブジェクトを解放し、weak pointerにnilを設定します。これは、nilに対する呼び出しが安全に行えるObjective-Cだからこそ生きる機能です。

弱参照(unsafe_unretained pointer)

もう一つの弱参照がunsafe_unretainedで、弱参照となり対象をretainしません。
ARCは強参照が0になると弱参照があってもオブジェクトを解放し、unsafe_unretainedには何もしません。。。呼ぶと危険なのでunsafeです。delegateや、Core Fundation系で遭遇します。

循環参照(retain cycle)

オブジェクト間の循環参照

二つのオブジェクトが相互に強参照をもってしまった場合、そのオブジェクトをARCが解放することはありません。ガベージコレタクのように孤立した循環参照を見つけて解放とかは一切ないので、プログラマの責任で循環参照を回避する必要があります。

基本的な対策として、相互に参照するオブジェクトには常に主従関係を考え、従->主の参照はweakポインタにしておきます。もし、主従関係を考えるのが難しい場合には、解放したいタイミングで参照ポインタをnilにする必要があります。

Blockによる循環参照

Blockはスコープ内で必要な変数への参照を持ちます。そのため、ブロック内で不用意にブロックの所有者を参照してしまうと循環参照となります。

対策は、、、一言で書くのがちょっと難しいです。よくあるケースとしてブロック内でselfを参照してしまうケースについては、weakなselfを作ることで対応できます。ただ、これは本当にケースバイケースです。