今日となりのチームの人と話してて、NSObjectにforwardInvocationというメソッドがある事を知りまして、ちゃんとObjective-Cも勉強して行こうという事で覚え書きです。
例えば以下のRubyのコードを実行すると、undefined methodエラーが発生します。
#!/usr/bin/ruby -Ku class Dog def bark puts 'Bow-wow!' end end dog = Dog.new dog.bark dog.walk
$ ruby method_missing.rb method_missing.rb:16: undefined method `walk' for #<Dog:0x102bb4448> (NoMethodError) Bow-wow!
ここでmethod_missingをオーバーライドすると、このエラーをフック出来ます。
class Dog def bark puts 'Bow-wow!' end def method_missing(method_name, *args, &block) puts "this dog can't #{method_name}!" end end
$ ruby method_missing.rb Bow-wow! this dog can't walk!
似た事はPythonでも可能で、__getattr__を利用して下記のような感じになります。(正確には違うけど)
#!/usr/bin/env python # -*- coding: utf-8 -*- class Dog: def __getattr__(self, method_name): def method_missing(): print "this dog can't %s!" % (method_name,) return method_missing def bark(self): print 'Bow-wow!' dog = Dog() dog.bark() dog.walk()
$ python method_missing.py Bow-wow! this dog can't walk!
んで、Objective-Cでも同じ事できるよっていうのが、forwardInvocationです。RubyとかPythonに比べるとややこしいのですけど、オブジェクトが対応するメソッドが存在しないメッセージを受け取った際に、このforwardInvocationが呼ばれます。後はフォールバック用のメソッドを用意して、そちらに流すとmethod_missingと同じ挙動を実現出来ますね。methodSignatureForSelectorも合わせてオーバーライドしておかないと、forwardInvocationが呼ばれないので注意しましょう。
#import <Foundation/Foundation.h> @interface Dog : NSObject - (void)bark; @end @implementation Dog - (void)bark { NSLog(@"Bow-wow!"); } - (void)method_missing:(NSString*)sel { NSLog(@"this dog can't %@!", sel); } - (void)forwardInvocation:(NSInvocation *)invocation { NSLog(@"%s", __func__); [self method_missing:NSStringFromSelector([invocation selector])]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { NSLog(@"%s", __func__); NSMethodSignature *sig = [super methodSignatureForSelector:sel]; if (!sig) sig = [[self class] instanceMethodSignatureForSelector:@selector(method_missing:)]; return sig; } @end int main(int argc, char* argv) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; Dog *dog = [[Dog alloc] init]; [dog bark]; [dog walk]; [pool release]; return 0; }
ちなみにforwardInvocationはNSProxyにも定義されています。プロキシでメッセージのディスパッチ先を動的に変更するって話ですね。仕組み自体がコンパイルと相性が良くないですけど、上手く使うと面白いコードが書ける気がしなくもないです。