2011年11月29日火曜日

NSInvocationでターゲットオブジェクトのメソッドコール

よく、オブジェクトの初期化メソッドで
[[AClass alloc] initWithTarget:self selector:@selector(hello:)];
などとしておいて、後で AClass から [self hello:…]をコールしてもらうということがありますが、これってどうやって実現しているんでしょうか?
方法はいくつかありますが、そのうちの一つが NSInvocation を使うという方法です。
cocos2d の内部でも使われていました。

NSInvocation オブジェクトの作成は以下のようになります。

- (id)initWithTarget:(id)tar selector:(SEL)sel
{
 ... 何かの処理
 invocation = nil;     // クラスメソッド NSInvocation *invocation
 if (tar && sel) {
  // selector(メソッド)のシグネチャを作成
  NSMethodSignature *sig = [tar methodSignatureForSelector:sel];
  if (sig) {
   invocation = [[NSInvocation invocationWithMethodSignature:sig] retain];
   [invocation setTarget:tar];
   [invocation setSelector:sel];
  }
 }
 ... 何かの処理
}
まず、引数の tar と sel を使用して、メソッドのシグネチャを作成します。
methodSignatureForSelector: は NSObject クラスのメソッドです。
tar クラス に sel メソッドが無ければ nil が返されます。

設定したメソッドを実行するには以下のようにします。
if (invocation) {
 [invocation setArgument:&arg1 atIndex:2];
 [invocation invoke];
}
arg1 は 例で言うと、target オブジェクトの hello メソッドに渡す引数です。
変数に&を付けてポインタ渡しするのと、引数のインデックスが2から始まることに注意です。

以下のように、引数を2つ以上必要な場合も大丈夫。
[[AClass alloc] initWithTarget:self selector:@selector(hello:world:)];
インデックスを指定して、いくつでも対応できます。
if (invocation) {
 [invocation setArgument:&arg1 atIndex:2];
 [invocation setArgument:&arg2 atIndex:3];
 [invocation invoke];
}

メソッドが返り値を持っている場合は、NSInvocation オブジェクトから取得します。
int value;
[invocation invoke];
[invocation getReturnValue:&value];  // int型の返り値の場合

似たような機能の実現方法としてデリゲートがありますが、プロトコルを定義しなければならず、そのようなおおげさな方法をとるまでもない時に NSInvocation は重宝します。

0 件のコメント:

コメントを投稿

Related Posts Plugin for WordPress, Blogger...