Objective-C runtime 拾遗 (一)——NSInvocation 调用Block

一日在开发之中,遇到这样一个问题,在某些场合,须要用NSInvocation来调用Block,而Block签名并非固定,即,Block参数类型个数能够不一样。html

问题

回忆NSInvocation 通常用法

天然想到了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

MethodSignature

一个问题是如何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.

其实上述代码问题不少,刚才有点撞大运编程

  1. selector(即_cmd)哪里去了?并无传递给NSInvocation

  2. 为何越界,按照文档说法应该从2开始。

  3. 为何从-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 连接

相关文章
相关标签/搜索