Swift & Objective-C

Objective-CのnilとJavaのnullの比較:なぜ動作が異なるのか

iOSを初めて触る開発者を最も戸惑わせることがあります。

読了 5 分
Objective-CのnilとJavaのnullの比較:なぜ動作が異なるのかのカバー画像

iOSを初めて触る開発者を最も戸惑わせることがあります。

JavaやC++では、nil(null)オブジェクトに何かをさせると、すぐにアプリが終了します。あの有名なNullPointerExceptionです。

しかしObjective-Cでは、nilにメッセージを送っても静かなままです。クラッシュもエラーも起きません。

初めは「これってバグでは?」と思いますが、実は言語が意図的にこのように設計されています。

結論から言うと、Objective-Cでnilにメッセージを送ると何も起こらず、単に0(またはnil)が返ります。

これはミスを放置しているのではなく、言語レベルで意図的に用意された安全策です。

今回は、なぜこの動作が可能なのか、そしてどう捉えるとよいのかを見ていきます。


nilへのメッセージ送信でクラッシュしないのはなぜ?

ポイントはObjective-Cのメッセージ送信方式にあります。

[object doSomething]のようにコードを書くと、コンパイラはこれをobjc_msgSend(object, @selector(doSomething))という関数呼び出しに変換します。

メソッドを直接呼ぶのではなく、「このオブジェクトにこのメッセージを届けて」とランタイムに依頼する仕組みです。

このobjc_msgSend関数は、最初に受信者がnilかどうかを確認します。

objc_msgSendがnilを静かに飲み込む場所
objc_msgSendがnilを静かに飲み込む場所

受信者がnilの場合は?

メソッドを探しに行かず、その場ですぐに0を返して静かに終了します。

つまりnilは「メッセージを飲み込む存在」です。送ってもどこにも届かないため、クラッシュもしません。

// receiverが nilならメソッドを探さず、直ちに 0 を返す
NSString *name = nil;
NSUInteger len = [name length];  // クラッシュせずに len == 0

NullPointerExceptionとは何が違うのか?

混同しやすい部分なので、表にまとめました。

項目 Objective-C (nil) Java (null)
nullへのメソッド呼び出し 無視して0/nilを返す NullPointerExceptionが発生
アプリの動作 クラッシュせず処理を継続 例外でクラッシュ
処理主体 ランタイム(objc_msgSend) JVMの例外処理

Javaではnull参照にアクセスした瞬間、「そのオブジェクトは存在しません」と例外が投げられます。

一方、Objective-Cランタイムはnilを正常な値として扱います。

そのため、Java開発者がObjective-Cに移ると、しばらくこの違いに戸惑います。

戻り値も型によって少し異なります。オブジェクト型ならnil、数値型なら0、構造体なら0で埋められた値が返ります。


クラッシュしないことは、必ずしも良いこと?

正直に言えば、両刃の剣です。

メリットは明確です。コードのあちこちにnilチェックを散りばめなくてもよいからです。

例えば配列が空でnilが返ってきても、countを尋ねれば単に0が返り、処理の流れは止まりません。

しかし、まさにこの点が落とし穴にもなります。

バグがクラッシュとして現れず、静かに埋もれてしまうのです。

画面にデータが表示されず長時間悩んでいると、途中でどこかのオブジェクトがnilになり、すべてのメッセージが無視されていることがよくあります。

クラッシュすればすぐ見つかるのに、静かに通り過ぎるため原因の追跡に時間がかかります。

実際に試せば本当にクラッシュしないかすぐ確認できます
実際に試せば本当にクラッシュしないかすぐ確認できます

そのため、nilが流れてもよい場所と、必ず値が必要な場所を開発者が見分ける習慣が重要です。


実務ではどう扱うとよい?

私が使っている方法をいくつか整理します。

  1. 重要な分岐ではnilを明示的にチェックする(if (object == nil)
  2. nilを返す可能性のあるメソッドは、コメントや名前で示しておく。
  3. 予期しないnilが疑われる場合は、NSAssertで開発段階に検出する。

特にSwiftへ移行すると、話はまた変わります。

SwiftはOptionalという概念によって、nilの可能性を型システムに明示します。

nilになり得る値には必ず疑問符を付け、アンラップするときも安全に扱うよう求められます。

言い換えると、SwiftはObjective-Cの「静かに通り過ぎる」を「事前に明示する」に変えたのです。

Swiftと並べて見ると、設計思想の違いが分かります
Swiftと並べて見ると、設計思想の違いが分かります

Objective-Cがnilへのメッセージでクラッシュしないのは、バグではなく思想です。

便利な一方で静かなバグも隠れてしまいますが、「なぜクラッシュしないのか」を理解すれば、より堅牢なコードを書けるようになります。

今、iOSコードで原因不明の空白画面に遭遇しているなら、途中でnilがひっそり通過していないか確認してみてください。きっと役に立ちます。

あわせて読みたい記事