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かどうかを確認します。
受信者が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が流れてもよい場所と、必ず値が必要な場所を開発者が見分ける習慣が重要です。
実務ではどう扱うとよい?
私が使っている方法をいくつか整理します。
- 重要な分岐ではnilを明示的にチェックする(
if (object == nil)) - nilを返す可能性のあるメソッドは、コメントや名前で示しておく。
- 予期しないnilが疑われる場合は、
NSAssertで開発段階に検出する。
特にSwiftへ移行すると、話はまた変わります。
SwiftはOptionalという概念によって、nilの可能性を型システムに明示します。
nilになり得る値には必ず疑問符を付け、アンラップするときも安全に扱うよう求められます。
言い換えると、SwiftはObjective-Cの「静かに通り過ぎる」を「事前に明示する」に変えたのです。
Objective-Cがnilへのメッセージでクラッシュしないのは、バグではなく思想です。
便利な一方で静かなバグも隠れてしまいますが、「なぜクラッシュしないのか」を理解すれば、より堅牢なコードを書けるようになります。
今、iOSコードで原因不明の空白画面に遭遇しているなら、途中でnilがひっそり通過していないか確認してみてください。きっと役に立ちます。

