Objective-C ランタイムで、method_getNumberOfArguments がセレクターが意味するよりも 2 つ多い結果を返すのはなぜですか?
たとえば、@selector(initWithPrice:color:) が 4 を返すのはなぜですか?
Objective-C ランタイムで、method_getNumberOfArguments がセレクターが意味するよりも 2 つ多い結果を返すのはなぜですか?
たとえば、@selector(initWithPrice:color:) が 4 を返すのはなぜですか?
大丈夫。記録をまっすぐにするために、はい、どの object-c メソッドへの最初の 2 つの引数もと でself
あり_cmd
、常にこの順序です。
ただし、より興味深いテーマは、このシナリオの理由です。そのためには、まず objc の歴史を調べる必要があります。さっそく始めましょう。
1983 年にさかのぼると、Objective-C の「神」である Brad Cox は、プラットフォーム間で優れたパフォーマンスと柔軟性を実現するために、Cの上にオブジェクト指向のランタイム ベースの言語を作成したいと考えていました。その結果、最初の Objective-C の「コンパイラ」は、Objective-C ソースを同等の C ランタイムに変換し、プラットフォーム固有の C コンパイラ ツールでコンパイルした単純なプリプロセッサでした。
ただし、C はオブジェクト用に設計されたものではありません。これは、Objective-C が克服しなければならない最も基本的なことでした。C は堅牢で柔軟な言語ですが、ランタイム サポートは C の重大な欠点の 1 つです。
Objective-C の非常に初期の設計段階で、オブジェクトは純粋にヒープベースのポインタ設計であることが決定されたため、奇妙なコピー セマンティクスなどを使用せずに任意の関数間でオブジェクトを渡すことができました (これは Obj-C++ で少し変更されました)。および ARC ですが、この投稿の範囲が広すぎます)、すべてのメソッドは自己認識している必要があります(実際、bbum が指摘しているように、元の関数呼び出しと同じスタック フレームを使用するための最適化でした)。理論的には、次のように複数のメソッド名を同じセレクターにマップすることができます。
// this is a completely valid objc 1.0 method declaration
void *nameOrAge(id self, SEL _cmd) {
if (_cmd == @selector(name)) {
return "Richard";
}
if (_cmd == @selector(age)) {
return (void *) (intptr_t) 16;
}
return NULL;
}
この関数は、理論的には 2 つのセレクターname
およびage
にマップされ、どちらが呼び出されたかに基づいて条件付きコードを実行できます。一般的な Objective-C コードでは、これはそれほど大きな問題ではありません。ARC では、キャストなどのために関数をセレクターにマップすることが非常に困難になっているためですが、言語はそれからかなり進化しています。
うまくいけば、 Objective-C メソッドへの 2 つの「見えない」引数の背後にある理由を理解するのに役立ちます。最初の引数は呼び出されたオブジェクトであり、2 番目の引数はそのオブジェクトで呼び出されたメソッドです。
最初の 2 つの引数は、隠し引数 self と _cmd です。