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 で参照することができます。そういう風にコード変換してるんですかね。

まあこんなところかな。