Objective-Cでnilにメッセージを送信する


107

AppleのObjective-C 2.0のドキュメントを読んでいるJava開発者として:「メッセージをnilに送信する」とはどういう意味か、それが実際にどのように役立つかは言うまでもありません。ドキュメントからの抜粋:

この事実を利用するCocoaにはいくつかのパターンがあります。メッセージからnilに返される値も有効な場合があります。

  • メソッドがオブジェクト、任意のポインター型、sizeof(void *)以下のサイズの整数スカラー、float、double、long double、またはlong longを返す場合、nilに送信されたメッセージは0を返します。
  • Mac OS X ABI関数呼び出しガイドで定義されているように、メソッドが構造体を返し、レジスタに返される場合、nilに送信されたメッセージは、データ構造のすべてのフィールドに対して0.0を返します。他の構造体データ型はゼロで埋められません。
  • メソッドが前述の値タイプ以外のものを返す場合、nilに送信されるメッセージの戻り値は未定義です。

Javaは私の頭脳を上記の説明にぶつけないようにしましたか?または、これをガラスのようにはっきりさせることができる欠けているものはありますか?

私はObjective-Cでメッセージ/レシーバーのアイデアを理解していnilますが、たまたまレシーバーについて混乱しています。


2
私もJavaのバックグラウンドを持っていて、最初はこの素晴らしい機能に怯えていましたが、今ではそれが絶対に素敵です。
Valentin Radu 2011

1
ありがとう、それは素晴らしい質問です。それの利点を見るためにあなたは完全に見ましたか?それは「バグではなく、機能」ではないかと思います。Javaが例外を除いて平手打ちをかけるだけのバグを抱え続けているので、問題がどこにあるのかわかっていました。ヌルポインター例外をトレードして、ほんの1行または2行のささいなコードをあちこちに保存するのは私にはうれしくありません。
MaciejTrybiło2012

回答:


92

まあ、私はそれは非常に工夫された例を使って説明できると思います。ArrayList内のすべての要素を出力するJavaのメソッドがあるとします。

void foo(ArrayList list)
{
    for(int i = 0; i < list.size(); ++i){
        System.out.println(list.get(i).toString());
    }
}

次に、そのメソッドを次のように呼び出すと、someObject.foo(NULL); リストにアクセスしようとすると、おそらくNullPointerExceptionが発生します。この場合は、list.size()の呼び出しです。さて、おそらくそのようなNULL値でsomeObject.foo(NULL)を呼び出すことはないでしょう。ただし、someObject.foo(otherObject.getArrayList());のようなArrayListを生成するエラーが発生した場合にNULLを返すメソッドからArrayListを取得した可能性があります。

もちろん、次のようなことをした場合にも問題が発生します。

ArrayList list = NULL;
list.size();

これで、Objective-Cには同等のメソッドがあります。

- (void)foo:(NSArray*)anArray
{
    int i;
    for(i = 0; i < [anArray count]; ++i){
        NSLog(@"%@", [[anArray objectAtIndex:i] stringValue];
    }
}

ここで、次のコードがあるとします。

[someObject foo:nil];

JavaがNullPointerExceptionを生成する同じ状況があります。nilオブジェクトは最初に[anArray count]でアクセスされますが、NullPointerExceptionをスローする代わりに、Objective-Cは上記のルールに従って単に0を返すため、ループは実行されません。ただし、ループを設定された回数実行するように設定すると、最初に[anArray objectAtIndex:i]のanArrayにメッセージが送信されます。これも0を返しますが、objectAtIndex:はポインターを返し、0へのポインターはnil / NULLであるため、ループを介して毎回NSLogにnilが渡されます。(NSLogは関数でありメソッドではありませんが、nil NSStringが渡されると(null)を出力します。

場合によっては、NullPointerExceptionを設定した方がよい場合があります。これは、プログラムに問題があることをすぐに確認できるためです。ただし、例外をキャッチしないと、プログラムはクラッシュします。(Cでは、この方法でNULLを逆参照しようとすると、プログラムがクラッシュします。)Objective-Cでは、代わりに実行時の動作が正しくない可能性があります。ただし、0 / nil / NULL /ゼロ化された構造体を返しても壊れないメソッドがある場合、これにより、オブジェクトまたはパラメーターがnilであることを確認する必要がなくなります。


33
この振る舞いが過去20年間にわたってObjective-Cコミュニティで多くの議論の的となってきたことは、おそらく言及する価値があります。「安全性」と「利便性」のトレードオフは、人によって評価が異なります。
Mark Bessey、

3
実際には、メッセージをnilに渡すことと、Objective-Cがどのように機能するかの間には、特にARCの新しいウィークポインター機能では、多くの対称性があります。弱いポインターは自動的にゼロに設定されます。0 / nil / NIL / NULLなどに応答できるようにAPIを設計してください
Cthutu

1
私は、あなたがしなければと思いmyObject->iVar、それは関係なく、それがCである場合、クラッシュするとともにまたはなしオブジェクト。(gravedigに申し訳ありません。)
11684

3
@ 11684正しいですが->、Objective-Cの操作ではなく、非常に一般的なC-ismです。
bbum 2013

1
最近のOSXのルートは、/隠されたバックドアのAPIが利用するためにOBJ-Cの無記号メッセージングのすべてのユーザー(管理者だけでなく)のためにアクセス可能です。
dcow


41

他のすべての投稿は正しいですが、ここで重要なのはそれがコンセプトかもしれません。

Objective-Cメソッド呼び出しでは、セレクターを受け入れることができるオブジェクト参照は、そのセレクターの有効なターゲットです。

これにより、「対象オブジェクトはタイプXですか?」というLOTが節約されます。コード-受信オブジェクトがセレクターを実装している限り、それがどのクラスであってもまったく違いはありませんnilセレクタを受け入れるNSObjectです- 何もしませ。これにより、多くの「nilをチェックし、trueの場合はメッセージを送信しない」コードも不要になります。(「受け入れる場合、それを実装する」という概念は、Javaインターフェースのようなプロトコルを作成できるようにするものでもあります。クラスが指定されたメソッドを実装する場合、プロトコルに準拠するという宣言です。)

これは、コンパイラーを幸せに保つ以外に何もしないサルのコードを排除するためです。はい、メソッド呼び出しのオーバーヘッドが1つ増えますが、プログラマー時間を節約できます。これは、CPU時間よりもはるかにコストのかかるリソースです。さらに、アプリケーションからより多くのコードと条件付きの複雑さを排除します。

ダウンボーターの明確化:これは良い方法ではないと思うかもしれませんが、それは言語の実装方法であり、Objective-Cで推奨されるプログラミングイディオムです(Stanford iPhoneプログラミング講義を参照)。


17

つまり、nilポインターでobjc_msgSendが呼び出されても、ランタイムはエラーを生成しません。代わりに、いくつかの(しばしば有用な)値を返します。副作用のあるメッセージは何もしません。

ほとんどのデフォルト値はエラーよりも適切であるため、これは便利です。例えば:

[someNullNSArrayReference count] => 0

つまり、nilは空の配列のように見えます。nil NSView参照を非表示にしても何も起こりません。便利ですか


12

ドキュメントからの引用には、2つの異なる概念があります-おそらく、ドキュメントがそれをより明確にした方が良いかもしれません:

この事実を利用するCocoaにはいくつかのパターンがあります。

メッセージからnilに返される値も有効な場合があります。

前者はおそらくここでより適切です。通常、メッセージを送信しnilてコードをより簡単にすることができます。どこでもnull値をチェックする必要はありません。正規の例はおそらくアクセサメソッドです:

- (void)setValue:(MyClass *)newValue {
    if (value != newValue) { 
        [value release];
        value = [newValue retain];
    }
}

送信メッセージがする場合はnil有効ではありませんでした、この方法はより複雑になります-あなたは確実にするために、2つの追加のチェックを持っている必要があるだろうvaluenewValueされないnilそれらのメッセージを送信する前に。

nilただし、後者の点(メッセージから返される値も通常は有効です)は、前者に乗数効果を追加します。例えば:

if ([myArray count] > 0) {
    // do something...
}

このコードもnil値のチェックを必要とせず、自然に流れます...

以上のことから、メッセージを送信できるという柔軟性nilはいくらか犠牲になります。値がである可能性を考慮に入れていなかったため、ある段階で奇妙な方法で失敗するコードを作成する可能性がありますnil


12

グレッグ・パーカーサイト

LLVMコンパイラ3.0(Xcode 4.2)以降を実行している場合

戻り値型を使用してnilするメッセージ| 返す
最大64ビットの整数| 0
長いdoubleまでの浮動小数点| 0.0
ポインター| なし
構造| {0}
任意の_Complexタイプ| {0、0}

9

安全のためにどこでもnilオブジェクトをチェックする必要がないことがよくあります-特に:

[someVariable release];

または、前述のように、nil値を取得すると、さまざまなcountおよびlengthメソッドはすべて0を返すため、nilのチェックを追加する必要はありません。

if ( [myString length] > 0 )

またはこれ:

return [myArray count]; // say for number of rows in a table

コインの反対側は、「if([myString length] == 1)」などのバグの可能性があることを覚えておいてください
ハトフィンチ

そのバグはどうですか?[myString length]は、myStringがnilの場合にゼロ(nil)を返します...問題となる可能性があるのは、[myView frame]です。
Kendall Helmstetter Gelner、2010

デフォルト値(0、nil、NO)が「役に立たない」という概念に基づいてクラスとメソッドを設計する場合、これは強力なツールです。長さをチェックする前に、文字列にnilがないかチェックする必要はありません。テキストを処理しているとき、空の文字列は役に立たず、nil文字列です。私はJava開発者でもあり、Javaの純粋主義者がこれを回避することを知っていますが、これにより多くのコーディングが節約されます。
Jason Fuerstenberg、2012

6

「レシーバーがゼロである」とは考えないでください。私は同意します、それかなり奇妙です。nilにメッセージを送信する場合、レシーバーはありません。何にもメッセージを送っていません。

これに対処する方法は、JavaとObjective-Cの哲学的な違いです。Javaでは、それはエラーです。Objective-Cでは、何もしません。


Javaでのその動作には例外があります。nullで静的関数を呼び出すと、変数のコンパイル時クラスで関数を呼び出すのと同じになります(nullかどうかは関係ありません)。
ローマンA.テイチャー、

6

nilに送信され、戻り値のサイズがsizeof(void *)より大きいObjCメッセージは、PowerPCプロセッサーで未定義の値を生成します。それに加えて、これらのメッセージは、未定義の値が、Intelプロセッサ上でもサイズが8バイトより大きい構造体のフィールドに返される原因になります。ヴィンセント・ゲーブルは彼のブログ記事でこれをうまく説明しています


6

他の回答でこれについて明確に述べたことはないと思います。Javaに慣れている場合は、Mac OS XのObjective-Cが例外処理をサポートしている一方で、これはオプションの言語機能であり、コンパイラフラグでオン/オフを切り替えます。私の推測では、この「メッセージの送信先nilは安全です」という設計は、言語に例外処理サポートが組み込まれる以前のものであり、同様の目標を念頭に置いて行われました。メソッドnilはエラーを示すために戻ることができます。nil通常はnilこれにより、エラー表示がコード全体に伝わるため、メッセージごとにチェックする必要がなくなります。重要な箇所で確認するだけです。私は個人的に、例外の伝播と処理がこの目標に対処するためのより良い方法だと思いますが、誰もがそれに同意するわけではありません。(一方で、たとえば、メソッドがスローする可能性のある例外を宣言する必要があるというJavaの要件は好きではありません。そのため、多くの場合、コード全体に例外宣言を構文的に伝播する必要がありますが、それについては別の議論です。)

同様の、しかしより長い、関連する質問への回答を投稿しました。「すべてのオブジェクト作成がObjective Cで成功したことを主張していますか?」詳細が必要な場合。


そのように考えたことはありません。これは非常に便利な機能のようです。
mk12

2
良い推測ですが、なぜ決定が下されたのかについて歴史的に不正確です。例外処理は最初から言語に存在していましたが、元の例外ハンドラーは現代のイディオムと比較するとかなり原始的でした。Nil-eats-messageは、SmalltalkのNilオブジェクトのオプションの動作から派生した意識的な設計の選択でした。オリジナルのNeXTSTEP APIが設計されたとき、メソッドチェーンは非常に一般的でありnil、チェーンをNO-opに短絡するためによく使用されていました。
bbum 2013

2

Cは、プリミティブ値の場合は0、ポインターの場合はNULL(ポインターコンテキストの0と同等)を表しません。

Objective-Cは、nilを追加することにより、Cの何も表現しないものに基づいて構築されます。nilは何もへのオブジェクトポインタではありません。意味的にNULLとは異なりますが、技術的には互いに同等です。

新しく割り当てられたNSObjectは、コンテンツが0に設定された状態で開始します。これは、オブジェクトが他のオブジェクトに持っているすべてのポインターがnilで始まるため、たとえば、initメソッドでself。(association)= nilを設定する必要がないことを意味します。

ただし、nilの最も顕著な動作は、メッセージを送信できることです。

C ++(またはJava)などの他の言語では、これによりプログラムがクラッシュしますが、Objective-Cでは、nilでメソッドを呼び出すとゼロの値が返されます。これにより、何かを行う前にnilをチェックする必要がなくなるため、式が大幅に簡略化されます。

// For example, this expression...
if (name != nil && [name isEqualToString:@"Steve"]) { ... }

// ...can be simplified to:
if ([name isEqualToString:@"Steve"]) { ... }

Objective-Cでnilがどのように機能するかを認識することで、この便利さが機能になり、アプリケーションに潜むバグではなくなります。nil値が望ましくない場合は、チェックして早期に失敗してサイレントに失敗するようにするか、NSParameterAssertを追加して例外をスローするようにしてください。

ソース:http : //nshipster.com/nil/ https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html(nilへのメッセージの送信)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.