Objective-C の季節 - メソッド、セレクタ
間違えて保存してしまったんですが、続き物ということにしてしまいます。
そんなわけでメソッドリストです。
(gdb) ptype struct objc_method_list
type = struct objc_method_list {
struct objc_method_list *method_next;
int method_count;
Method method_list[1];
}
単なるリストかと思ったら、リストのリストになってるみたいですね。
$78 = {method_next = 0x80571f8, method_count = 1, method_list = {{method_name = 0x442500, method_types = 0x4419ac "C12@0:4*8",
method_imp = 0x2753b0}}}
(gdb) p *obj->isa->class_pointer->class_pointer->methods->method_next
$79 = {method_next = 0x4596e0, method_count = 3, method_list = {{method_name = 0x4a4ac8, method_types = 0x458b3b "C12@0:4:8",
method_imp = 0x2c22f0}}}
(gdb) p *obj->isa->class_pointer->class_pointer->methods->method_next->method_next
$80 = {method_next = 0x804c1e8, method_count = 3, method_list = {{method_name = 0x4a53e8, method_types = 0x458b05 "@8@0:4",
method_imp = 0x2c18d0}}}
(gdb) p *obj->isa->class_pointer->class_pointer->methods->method_next->method_next->method_next
$81 = {method_next = 0x148b00, method_count = 38, method_list = {{method_name = 0x804bad8, method_types = 0x14873e "@8@0:4",
method_imp = 0x13c0c0}}}
(gdb) p *obj->isa->class_pointer->class_pointer->methods->method_next->method_next->method_next->method_next
$82 = {method_next = 0x0, method_count = 11, method_list = {{method_name = 0x804ba08,
method_types = 0x148620 "i12@0:4^{objc_typed_stream=^v^{cache}^{cache}^{cache}^{cache}iiii^?^?^?^?}8", method_imp = 0x13cd40}}}
うーん、何と対応しているリストなのか、なんともよくわかりません…継承関係?にしても Object クラスだしなあ…まあ Objective-C の場合、メソッドはクラスだけでなく、カテゴリとかが追加することもあるようなので、その辺かもしれません。あとでクラス追加して色々やる段階になれば分かるでしょう。
そんなわけでメソッドです。
(gdb) ptype Method
type = struct objc_method {
SEL method_name;
const char *method_types;
IMP method_imp;
}
(gdb) ptype SEL
type = const struct objc_selector {
void *sel_id;
const char *sel_types;
} *
(gdb) ptype IMP
type = struct objc_object {
struct objc_class *class_pointer;
} *(*)(struct objc_object *, SEL, ...)
メソッドは、メソッドを指し示すためのセレクタと、型情報と、その実装を指す関数ポインタから構成されているらしいです。順当な感じですね。
さてメソッド名はどこだろう、ということで SEL 型を見るわけですが、sel_id は void* なので、どういう物なのか分かりません。とりあえず値を見てみます。
(gdb) p *obj->isa->class_pointer->class_pointer->super_class->methods->method_list->method_name
$108 = {sel_id = 0xc0047, sel_types = 0x4419ac "C12@0:4*8"}
なんなんだろこれ、はてー <(。ε°)>
分からないものは仕方がないので、教えてグーグル先生!
__sel_register_typed_name (const char *name, const char *types, struct objc_selector *orig, BOOL is_const) { struct objc_selector* j; sidx i; struct objc_list *l; i = (sidx) hash_value_for_key (__objc_selector_hash, name); if (soffset_decode (i) != 0) { for (l = (struct objc_list*)sarray_get_safe (__objc_selector_array, i); l; l = l->tail) { SEL s = (SEL)l->head; if (types == 0 || s->sel_types == 0) { if (s->sel_types == types) { if (orig) { orig->sel_id = (void*)i;
ふむー。
真面目にソースコード参照しながら説明するのは面倒なので、簡単に説明すると、全てのセレクタとその名前は、ランタイムが一元管理してしまいます。objc_selector::sel_id は、データ構造内部への参照を表す値になっていて、それを利用して、セレクタや名前が取れる、と。データ構造は sparse array といわれる…まあハッシュテーブルです!つまり sel_id はメソッド名をハッシュ関数でハッシュした値から作られた、バケットのインデックスと、バケット内のインデックスです。
それで、結局どうやったらメソッド名分かるんです、ということなんですが、sel_get_name という関数で取得できます。
(gdb) p (const char*)sel_get_name(obj->isa->class_pointer->class_pointer->methods->method_list[0].method_name)
$4 = 0x441992 "_conformsToProtocolNamed:"
やりましたー。
ちなみに普通の Mac の環境だと、SEL は const char* らしいです。確かめてないので何ともいえませんが確かめました。snow leopard では文字列でした。詳細はこの辺参照してください。GNU の方がこうなってるのは、ハッシュ関数の呼び出しを減らすため、とかかなあ?
そんなわけなので、よいこの皆は NSStringFromSelector を使いましょう。
で、IMP 型ですが、みたまんまの関数ポインタ型なのでそういうことです。
こんなの書いて
@interface Hoge : NSObject { } - (int)incr:(int)i; @end @implementation Hoge - (int)incr:(int)i { return i + 1; } @end
使ってみます。
(gdb) p hoge->isa->methods->method_list->method_imp(hoge, hoge->isa->methods->method_list->method_name, 1)
$5 = (struct objc_object *) 0x2
第一引数、第二引数、は使ってないので、こんなことも。
(gdb) p hoge->isa->methods->method_list->method_imp(0, 0, 1)
$12 = (struct objc_object *) 0x2
ヒエ〜ッ…
第一引数は所謂 self だからいいとして、第二引数の SEL は何で引数に取ってるんでしょうね…うーん。何かこう、別クラスのメソッドへ転送する機能とかあったっけ…ないよなあ…プロパティの実装の共通化とかで使えそうだけど…うーん。どういうところで使われてるのか、知ってる人いたら教えてください…
追記:forward メソッドの呼び出し時とか、普通に活用されてますね。forward メソッドは ruby でいうところの method_missing です
現在のメソッド呼び出しに使われたセレクタは、コード上では _cmd で参照することができます。そういう風にコード変換してるんですかね。
まあこんなところかな。