variadic method (その3)
可変引数を使った任意のメソッドの呼び出しはようやく形になってきたが、C言語の経験値の低さ故にはまることが多い。
同メソッドだが、引数の型に合わせてva_listから値を取り出さなくてはならないため、va_listをメソッド間で引き渡す必要が出てくる。
NSObject+MethodUItil
+(id)invokeInnerFor:(id) object byNamed:(NSString*)name firstArg:(void*) firstArg argList:(va_list) list { SEL selector = NSSelectorFromString(name); NSMethodSignature* sig = [object methodSignatureForSelector: selector]; NSInvocation* inv = [NSInvocation invocationWithMethodSignature:sig]; [inv setTarget: object]; [inv setSelector:selector]; unsigned int argCount = [sig numberOfArguments]; for ( int index = 2 ; index < argCount; index++) { const char* argType = [sig getArgumentTypeAtIndex:index]; [self setArgFromVarList:inv forIndex:index forFirstArg:firstArg argList:list forArgType:argType]; } [inv invoke]; //実行 //戻り値の取得 } + (void)setArgFromVarList:(NSInvocation*) invocation forIndex:(int) index forFirstArg:(void*) firstArg argList:(va_list) list forArgType:(const char *)type { //Object(id)型の場合 { id arg = index == 2 ? (__bridge id)firstArg : va_arg(list, id); [invocation setArgument:(void*)&arg atIndex:index]; break; } 〜 パラメタの型に合わせた処理の記述が必要だが省略 }
細かい処理は端折っているが実際にはva_listにはいろいろな型のパラメタがセットされてくるため、それを判定して適切にセットする処理が必要だが、長くなるために省略している。
ここで問題にになるのは、invokeInnerFor:からsetArgFromVarList:に引き渡すva_listの扱いである。このままでは何度va_argを呼び出しても最初のパラメタしか取得することが出来ない。Cの経験値の高い方はすぐにその原因が分るだろう。
va_listはオブジェクト型ではなく通常の型(恐らくは構造体と思われる)のため、メソッドで引き渡すと値がコピーされて引き渡されるためva_listの列挙(va_arg)はループの際に一度しか実行されない。
+ (void)setArgFromVarList:(NSInvocation*) invocation forIndex:(int) index forFirstArg:(void*) firstArg argList:(va_list*) list forArgType:(const char *)type { 〜 id arg = index == 2 ? (__bridge id)firstArg : va_arg(*list, id); 〜 }
ということでアドレス渡しにすることで期待通りに動作した。
Objective-Cでいろいろ書いているつもりが、気がつくとCのコーディングをしているという...
NS_REQUIRES_NIL_TERMINATIONはマクロであり、これを記述しておくとパラメタの末尾をnilでターミネートしないとXcode側で"Missing sentinel in method dispatch"という警告を出してくれる。