ARC で IMP を使うとよく落ちるんですけど、なぜ落ちるかというと、
typedef id (*IMP)(id, SEL, ...);
IMP はこのように返り値が id 型の関数として定義されていまして、オブジェクト以外の値を返す IMP に対しても問答無用で retain してしまい、結果落ちるというわです。
適切にキャストしてやればよいのですが、ただのキャストではだめで __bridge を使って「retain するな」ということを明示しなければなりません。
NSInteger result=(NSInteger)(__bridge void*)imp(self, _cmd);
このコードはめでたく落ちないんですけど、返り値が id 型より大きい double や NSRect なんかだとこういう風にキャストできない。
じゃあどうすればいいか。受け取った id の型をキャストするのではなく、関数定義そのものをキャストしてしまおうというのが今回のポイントです。
NSRect result=((NSRect(*)(id, SEL, ...))imp)(slf, sel);
こうやって「お前は IMP じゃない、 NSRect を返す関数なんだ」と言い聞かせることによってコンパイルを通し、正常に動作するコードになりました。しかしこれ括弧の量が尋常ではないです。わけが分かりません。もっとすっきり書きたい。そこで登場するのが 共用体 です。
共用体、またの名を union。C 言語を学び始めたごく初期に構造体と共に登場する概念。仕組みは理解できても使いどころが思いつかず、引き出しの奥にしまい込まれる可哀想な存在。共用体の出番がついにやってきました。
typedef union KZRIMPUnion {
IMP as_id;
void (*as_void)(id, SEL, ...);
void* (*as_pointer)(id, SEL, ...);
char (*as_char)(id, SEL, ...);
int (*as_int)(id, SEL, ...);
long long (*as_long_long)(id, SEL, ...);
double (*as_double)(id, SEL, ...);
CGFloat (*as_float)(id, SEL, ...);
CGRect (*as_rect)(id, SEL, ...);
CGSize (*as_size)(id, SEL, ...);
CGPoint (*as_point)(id, SEL, ...);
NSRange (*as_range)(id, SEL, ...);
} KZRIMPUnion;
これが私の C 言語史上はじめて自分で定義した共用体です。これに IMP を入れて様々な形式で取り出そうという作戦です。これを使うと、
KZRIMPUnion impUnion;
impUnion.as_id = imp;
NSRect result=impUnion.as_rect(slf, sel);
なんと、こんなにすっきり記述できます。一切キャストする必要がないのです。共用体ってすばらしい。
ちなみにこれを利用して Method Swizzling ライブラリを作りました。Block を使い定義とコードをすべて1箇所に集約できる超イケてるライブラリです。
hetima/KZRMethodSwizzling