Blocksをメソッドとして追加する
imp_implementationWithBlock()という、BlockからIMPを作り出す関数がiOS 4.3 or Lionから使えるようになったみたいなので、これを使って実行時にインスタンスメソッドを追加する実験
例えば、モーダルウィンドウをクローズした時に自動的にstopModalするコードは下のようになる。簡単。*1
/* CLANG_ENABLE_OBJC_ARC = YES */ #import <Foundation/Foundation.h> #import <AppKit/AppKit.h> #import <objc/runtime.h> ... NSApplication *application = [NSApplication sharedApplication]; BOOL (^windowShouldClose)(id, id) = ^(id self, id sender) { NSLog(@"windowShouldClose:%@", sender); if ([application modalWindow] == self) { [application stopModal]; } return YES; }; // Block to IMP IMP imp_windowShouldClose = imp_implementationWithBlock((__bridge void*) windowShouldClose); // add IMP to class as Method class_addMethod([NSWindow class], @selector(windowShouldClose:), imp_windowShouldClose, "B@:@"); ...
Blockの引数は、第一引数がid型(selfが渡される)で、第二引数以降がメソッドの引数の列になる。戻り値の型はメソッドの戻り値の型を指定する。この場合、windowShouldClose:はid型の引数を取り、戻り値の型はBOOLなので、「BOOL (^)(id, id)」になるようにBlockを定義する。
class_addMethod()の最後の引数は、メソッドのシグネチャ文字列(NSMethodSignatureの+signatureWithObjCTypes:の引数と同じ)で、「戻り値がBOOL(="c")で引数がid(="@")」なので、"c@:@"となる*2。以下も参照。
既存のメソッドの実装を置き換えることも出来る。置き換え元の実装を呼び出しやすくするために「Blockを作るBlock」を渡せるようにしてみる。
void replaceMethodWithBlock(Class cls, SEL sel, id (^blockGen)(SEL, IMP)) { IMP originalImplementation = NULL; Method originalMethod = class_getInstanceMethod(cls, sel); if (!originalMethod) { [NSException raise:NSInvalidArgumentException format:@"method not found:%s", sel]; } originalImplementation = method_getImplementation(originalMethod); id block = blockGen(sel, originalImplementation); if (!block) { return; } IMP imp = imp_implementationWithBlock((__bridge void*) block); method_setImplementation(originalMethod, imp); }
これを使って、例えばNSWindowのmouseDown:の前後にログを吐くようにする。
replaceMethodWithBlock([NSWindow class], @selector(mouseDown:), // block to generate block for method. (id) ^(SEL selector, IMP originalImplimentation) { // block for method. void (^block)(id, NSEvent*) = ^(id self, NSEvent *theEvent) { NSLog(@"[BEFORE] mouseDown:%@", theEvent); originalImplimentation(self, selector, theEvent); NSLog(@"[AFTER] mouseDown:"); }; return block; });
ちょっとAOPっぽい感じ。
なんかちょっとメモリ管理回りが不安だけど。[block copy]とかやんなくて良くなったのかな?
おまけ: 関数の型について
IMPの型はヘッダによるとこう
typedef id (*IMP)(id, SEL, ...);
一方Blockの関数部分はこんな感じ(cf: http://d.hatena.ne.jp/terazzo/20101103/1288807194)
id (struct __block_literal_1 *, ...)
引数の数だけで言えばBlock側はSELが足りない。メソッド用Blockの第二引数がSELではないのは、この辺と関係ありそう。
というようなこと考えてたら、解説があったのでリンクしておく。中身まだあんまり読めてないけど。