Objective-C
Objective-Cは何故メソッド呼び出しの度にメソッドを検索するのか
Objective-Cには「メソッド呼び出しの度に、目的のメソッドを検索する」という仕様がある。
さらにインスタンス変数(メンバ変数)はすべてPrivateなので、C++のように外部から直接アクセスすることができず、必ずメソッドを経由することになる。
そのため、あるオブジェクトのインスタンス変数の値を読むだけでも、ゲッターメソッドを検索して、見つけたら呼び出して、やっと値が得られるという動きになる。
オブジェクト内にあるメソッド開始点のアドレスが分かればそこへジャンプ(コール)すれば良いだけの話なのだが、どうしてそんなパフォーマンスが落ちるような仕様になっているのだろうか。(実際C++の場合はメンバ関数を検索することなく関数開始アドレスへジャンプ(コール)する。標準C++でそういう取り決めがあるわけではないと思うが)
この理由は、C++とObjective-Cのオブジェクトシステムの違いにある。
C++はint型やlong型はもちろんのこと、構造体やクラスも「型」があるので、オブジェクト(構造体やクラスのインスタンス)にも当然それぞれの型がある。
C++ではHogeクラスのインスタンスは、Hoge型の変数(またはポインタ変数やスマートポインタ)にしか格納できない。
もしHogeクラスのインスタンスが入っていた変数に、Piyoクラスのインスタンスを代入しようとすると、コンパイルエラーになる。(継承関係にあるインスタンス同士ならキャストが可能だが)
普段C++やJavaなどを書いている人なら「何を当たり前のことを」と思うだろう。
しかしObjective-Cはそれが当たり前ではない。
Objective-Cにもint型やlong型などC++と同じ型があるが、オブジェクト(@interfaceで定義したクラスのインスタンス)はすべて「id型」という型になる。(「id型」の中身は「void *」つまりvoid型ポインタだが、現在ではこの2つは明確に区別されていて、インスタンスをvoid型のポインタ変数に格納することは推奨されていない)
Objective-CはHogeクラスのインスタンスでもPiyoクラスのインスタンスでも、オブジェクトならすべてid型にすることができる。
つまり、Hogeクラスのインスタンスが入っていた変数に、Piyoクラスのインスタンスを代入すると、まるで動的型付け言語のように、すんなり上書きできてしまうわけだ。
Objective-Cの強みは、このオブジェクトの柔軟さにある。
Objective-Cのオブジェクト(クラスのインスタンス)の正体は、Cの構造体そのものだ。
Cのように(というかCなので)structをmalloc(sizeof(Hoge))した構造体も作れるし、@interfaceを[Hoge alloc]したオブジェクトも作れるのだが、id型になるのは後者だけだ。
本来データの塊であるCの構造体を間借りする形で処理(メソッド)を入れ込み、その先頭アドレスをid型に格納したものが、C側から見たオブジェクトということになる。
ここでstructをmalloc(sizeof(Hoge))した構造体を「C構造体」、@interfaceを[Hoge alloc]したオブジェクトを「OCオブジェクト」と呼ぶことにする。
C構造体にはそれぞれの型がある。
「型」とは簡単に言うと「そのデータの大きさと構造を知るための情報」だ。
C++コンパイラはコンパイル時にこの型情報を保持し、型に従ってCPUのアドレッシングモードと命令を組み合わせたマシンコードを生成するので、インスタンス内のメンバ変数やメンバ関数に一発でアクセスできるコードができあがる。(これが静的型付け)
例えば、C構造体内にある配列の要素にアクセスするためには、ベースレジスタがC構造体の先頭アドレスを持ち、ディスプレースメントが配列の先頭までの距離、インデックスレジスタが配列の要素までの(スケーリングされる前の)距離を持つ、といった具合だ。
しかしOCオブジェクトは生成された瞬間にid型となり、本当の型はOCオブジェクト内のメソッドからしか分からない状態になってしまう。
OCオブジェクトのインスタンス変数にアクセスできるのは、本当の型が分かるOCオブジェクト内のメソッドだけであり、そこでは「self->x」などとして(静的型付けで)自身のインスタンス変数にアクセスができる。
しかしC側からはOCオブジェクトの型が(ただのアドレス値にしか)見えないので、その中身が分からない。(OCオブジェクト内のどこに何があるのかも、OCオブジェクトがどのくらいの大きさなのかも分からない)
つまりどういったアドレッシングモードを使ってアクセスすれば良いのか分からないので、C側から一発でインスタンス変数にアクセスするためのマシンコードがコンパイル時に生成できない。
全インスタンス変数(メンバ変数)がPrivateなのは、この仕組み上どうしても「そうなってしまった」わけだ。
変数がPrivate状態になっても、アクセサメソッドを実装すればアクセスできるし、これこそが「カプセル化」なので、そこは大きな問題ではない。
しかし困ったのがメソッド呼び出しだ。
何度も書いたように、OCオブジェクトはC側から見るとid型(ただのアドレス値)なので、そのアクセサメソッドすらもPrivate状態になってしまっている。
これではただメモリを圧迫するだけの塊であり使い物にならないので、ランタイムにメソッドを「探させる」しかない。
つまり、メソッドを呼び出すたびにメソッドを検索する、という方法以外にメソッドを見つける手段が無いのだ。
余談ではあるが、この仕様から、厳密な意味での「Publicメソッド / Privateメソッド」という区別も(構造的には全メソッドがPrivateであり、検索したらどんなメソッドも見つかってしまうので)Objective-Cには存在しない。
さらにデータアクセスの仕組みとメソッド呼び出しの仕組みがまったく違うので、C++では不可能な「インスタンス変数名とメソッド名を同じにする」ということができる。
C++だと変数と関数は同名で定義できないので、オブジェクト「foo」内の「bar」というメンバ変数のゲッターはどうしても「get_bar」などになってしまうが、Objective-Cなら「bar」という名前のゲッターを作ることが可能であり、実際そういったネーミングが推奨されている。
Objective-Cは見た目のオブジェクトの型をid型(単なるアドレス値)として統一し、動的型付けのように扱いやすくした。
その代わりに、「このオブジェクトの大きさはどのくらいで、どこにどんなインスタンス変数やメソッドがあるのか」という情報を隠蔽してしまう、という代償と引き換えることとなった。
その結果生まれたのが、「メソッド呼び出しのたびに毎回メソッドを検索する」という動作となったわけだ。
|