5

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

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;
    }
}
4

3 回答 3

7

好吧,您在这里遇到了 C 中经典的缺少元数据和 ABI 问题。基于 Mike Ash关于 MABlockClosure 的 Awesome Article,我认为您可以检查块的底层结构并假设 va_list 与块的预期匹配。您可以将块转换为 struct Block_layout,然后 block->descriptor 将为您提供 struct BlockDescriptor。然后你有代表块的参数和类型的@encode 字符串(@encode 是一个完整的其他蠕虫罐)。

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

让我添加一个大胖子“这里有龙”警告。这一切都非常棘手,因 ABI 而异,并且依赖于晦涩和/或未记录的功能。

似乎您也可以在需要的地方直接调用该块,可能使用 NSArray 作为唯一参数,使用 id 作为返回类型。然后,您不必担心任何“聪明”的黑客攻击会适得其反。

编辑:您也许可以使用 NSMethodSignature 的 signatureWithObjCTypes:,传入块的签名。然后你可以调用 NSInvocation 的 invocationWithMethodSignature:,但是你必须调用私有的 invokeWithIMP: 方法才能真正触发它,因为你没有选择器。您将目标设置为块,然后调用WithIMP,传递块结构的调用指针。请参阅通用块代理

于 2013-01-13T03:58:03.143 回答
0

使用私有 invokeWithIMP: 的一个很好的替代方法是调配您想要具有不同实现的方法,并使其在调用时查找所需的 IMP。我们使用类似的东西:https ://github.com/tuenti/TMInstanceMethodSwizzler

于 2014-03-31T09:23:10.660 回答
0

真正的解决方案是拥有一个带有 va_list 参数的块,并让该块自行排序。这是一种行之有效且简单的方法。

于 2014-03-31T09:30:29.683 回答