当块的参数数量和类型可以变化时,使用来自va_list的参数调用块

时间:2012-12-04 12:14:40

标签: objective-c ios call objective-c-blocks

我有一个变量,块接受一些参数。参数的确切数量及其类型可以有所不同。例如,它可以是块

void(^testBlock1)(int) = ^(int i){}

或块

void(^testBlock2)(NSString *,BOOL,int,float) = ^(NSString *str,BOOL b,int i,float f){}

参数类型仅限于{id, BOOL, char, int, unsigned int, float}

我知道当前的参数数量及其类型。我需要实现一个可以使用给定参数执行块的方法:

-(void)runBlock:(id)block withArguments:(va_list)arguments 
          types:(const char *)types count:(NSUInteger)count;

我有一个天真的解决方案,但它非常难看,只支持不超过4个字节大小的类型并且依赖于对齐。所以我正在寻找更好的东西。 我的解决方案是这样的:

#define MAX_ARGS_COUNT 5
-(void)runBlock:(id)block withArguments:(va_list)arguments 
          types:(const char *)types count:(NSUInteger)count{

    // We will store arguments in this array.
    void * args_table[MAX_ARGS_COUNT];

    // Filling array with arguments
    for (int i=0; i<count; ++i) {
        switch (types[i]) {
            case '@':
            case 'c':
            case 'i':
            case 'I':
                args_table[i] = (void *)(va_arg(arguments, int));
                break;
            case 'f':
                *((float *)(args_table+i)) = (float)(va_arg(arguments, double));
                break;
            default:
                @throw [NSException exceptionWithName:@"runBlock" reason:[NSString stringWithFormat:@"unsupported type %c",types[i]] userInfo:nil];
                break;
        }
    }

    // Now we need to call our block with appropriate count of arguments

#define ARG(N) args_table[N]

#define BLOCK_ARG1 void(^)(void *)
#define BLOCK_ARG2 void(^)(void *,void *)
#define BLOCK_ARG3 void(^)(void *,void *,void *)
#define BLOCK_ARG4 void(^)(void *,void *,void *,void *)
#define BLOCK_ARG5 void(^)(void *,void *,void *,void *,void *)
#define BLOCK_ARG(N) BLOCK_ARG##N

    switch (count) {
        case 1:
            ((BLOCK_ARG(1))block)(ARG(0));
            break;
        case 2:
            ((BLOCK_ARG(2))block)(ARG(0),ARG(1));
            break;
        case 3:
            ((BLOCK_ARG(3))block)(ARG(0),ARG(1),ARG(2));
            break;
        case 4:
            ((BLOCK_ARG(4))block)(ARG(0),ARG(1),ARG(2),ARG(3));
            break;
        case 5:
            ((BLOCK_ARG(5))block)(ARG(0),ARG(1),ARG(2),ARG(3),ARG(4));
            break;
        default:
            break;
    }
}

3 个答案:

答案 0 :(得分:6)

你在这里遇到C中经典的元数据缺乏和ABI问题。基于Mike Ash的Awesome Article about MABlockClosure,我认为您可以检查块的底层结构,并假设va_list与块期望的匹配。您可以将块转换为struct Block_layout,然后block-&gt;描述符将为您提供struct BlockDescriptor。然后你有@encode字符串代表块的参数和类型(@encode是另一种蠕虫病毒)。

因此,一旦获得了参数列表及其类型,就可以深入了解block_layout,抓取调用,然后将其视为函数指针,其中第一个参数是提供上下文的块。如果您不关心任何类型信息,Mike Ash还有一些关于Trampolining Blocks可能有用的信息,但只想调用该块。

让我添加一个大胖子“这里有龙”警告。这一切都非常敏感,根据ABI而有所不同,并且依赖于模糊和/或无证的功能。

似乎你可以直接在需要的地方调用块,可能使用NSArray作为唯一参数,id作为返回类型。那么你就不必担心任何“聪明”的黑客会对你做好事。

编辑:您可以使用NSMethodSignature的signatureWithObjCTypes :,传入块的签名。然后你可以调用NSInvocation的invocationWithMethodSignature:,但你必须调用私有invokeWithIMP:方法来实际触发它,因为你没有选择器。您将目标设置为块,然后invokeWithIMP,传递Block结构的调用指针。见Generic Block Proxying

答案 1 :(得分:0)

使用私有invokeWithIMP的一个很好的替代方法是调用你想要拥有不同实现的方法,并在调用它时随时查找所需的IMP。我们使用类似的内容:https://github.com/tuenti/TMInstanceMethodSwizzler

答案 2 :(得分:0)

真正的解决方案是使用带有va_list参数的块并让块对其自身进行排序。这是一种经过验证的简单方法。