一日在開發之中,遇到這樣一個問題,在某些場合,須要用NSInvocation來調用Block,而Block簽名並非固定,即,Block參數類型個數能夠不一樣。html
天然想到了NSInvocation,譬如以下代碼:ios
NSString* string = @"Hello"; NSString* anotherString = [string stringByAppendingString:@" World!"];
寫成Invocataion大體是這樣的:git
NSString* string = @"Hello"; NSString* anotherString; NSString* stringToAppend = @" World!"; NSInvocation* inv = [NSInvocation invocationWithMethodSignature:[NSString instanceMethodSignatureForSelector:@selector(stringByAppendingString:)]]; inv.target = string; [inv setArgument:&stringToAppend atIndex:2]; [inv invoke]; [inv getReturnValue:&anotherString];
具體就不詳細介紹了,文檔裏講得很詳細。請移步Apple Docgithub
一個問題是如何Block得到MethodSignature。Block沒有selector,但發現NSMethodSignature有這樣一個方法-[NSMethodSignature signatureWithObjCTypes:]
,那問題轉化成如何從Block得到編碼的Signature。objective-c
一搜索。發現Clang官方文檔和stackoverflow都有說這個問題。(Clang官方文檔真是個寶庫啊)。shell
按Clang的文檔,Block定義以下:編程
struct Block_literal_1 { void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor_1 { unsigned long int reserved; // NULL unsigned long int size; // sizeof(struct Block_literal_1) // optional helper functions // void (*copy_helper)(void *dst, void *src); // IFF (1<<25) // void (*dispose_helper)(void *src); // IFF (1<<25) // required ABI.2010.3.16 // const char *signature; // IFF (1<<30) void* rest[1]; } *descriptor; // imported variables };
中間註釋部分是對作了些小改造,由於對於能夠copy的Block,上述兩個函數指針才存在。(另外,發現其實Block還能經過block->invoke(...)
來調用,先按下不表)。segmentfault
static const char *__BlockSignature__(id blockObj) { struct Block_literal_1 *block = (__bridge void *)blockObj; struct Block_descriptor_1 *descriptor = block->descriptor; int copyDisposeFlag = 1 << 25; int signatureFlag = 1 << 30; assert(block->flags & signatureFlag); int offset = 0; if(block->flags & copyDisposeFlag) offset += 2; return (const char*)(descriptor->rest[offset]); } NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:__BlockSignature__(block)]];
最重要的一個問題解決了。以後就是對invocation調用setArgument,進行參數傳遞。(先簡化一下問題,參數都是NSObject,放在NSArray裏,關於參數獲取後面還有坑,之後再寫)app
for(NSUInteger i = 0; i < args.count ; ++i){ id object = args[i]; [invocation setArgument:&object atIndex:i + 2]; } [invocation invokeWithTarget:block];
調用,Crash!越界了函數
Reason: -[NSInvocation setArgument:atIndex:]: index (5) out of bounds [-1, 4]
文檔是這樣描述setArgument
的
Indices 0 and 1 indicate the hidden arguments self and _cmd, respectively; these values can be retrieved directly with the target and selector methods. Use indices 2 and greater for the arguments normally passed in a message.
其實上述代碼問題不少,剛纔有點撞大運編程
selector(即_cmd
)哪裏去了?並無傳遞給NSInvocation
爲何越界,按照文檔說法應該從2開始。
爲何從-1開始
文檔說的言之鑿鑿,第一個參數傳self,第二個是selector(即_cmd
),但block調用並無selector,參數個數其實能夠從MethodSignature獲取:invocation.methodSignature.numberOfArguments
因此這就是會越界的緣由,正確的作法是從1開始:
[invocation setArgument:&object atIndex:i + 1]
一調試,果真。
另外從-1開始緣由是-1的位置是存儲return result,固然這個結論我查了文檔並無找到,也是試出來的。囧。
源碼我放在了github,戳這裏
用法也很簡單:
NSInvocation* inv = [NSInvocation invocationWithBlock:block];
後續會增長一些接口如:+ (instancetype) invocationWithBlockAndArguments:(id) block ,...;
恩 已經增長了。
增長的接口用法:
對於
void (^myBlock)(id, NSArray*,double, int**) = ^(id obj1, NSArray* array, double dNum,int** pi) { NSLog(@"%@",@"Hey!"); };
int* i = NULL; NSInvocation* inv = [NSInvocation invocationWithBlockAndArguments:myBlock,[NSObject new],@[@1,@2,@3],1.23,&i];
參數支持id
,全部簡單值類型,IMP
,SEL
,Class
,Block
,指針
, 但Struct,Union,C-style Array 不支持,比較預想的tricky,研究中。
原做寫於segmentfault 連接