【问题标题】:Call block with arguments from va_list when block's arguments number and types can vary当块的参数数量和类型可以变化时,使用来自 va_list 的参数调用块
【发布时间】:2012-11-22 01:32:33
【问题描述】:

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

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

【问题讨论】:

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


    【解决方案1】:

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

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

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

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

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

    【讨论】:

    • 感谢您的回答。我已经有了参数列表及其类型。是的,我可以找到并获取 invoke 函数指针。但是,我怎样才能以比我的 BLOCK_ARG(N) 用于强制转换的宏和用于参数的临时数组更好的方式来特别调用它呢?除了ffi_call,还有其他解决方案吗?对于我的小任务来说,使用 libffi 看起来开销太大。
    • 嗯,事情就是这样——你必须使用汇编来以 C 期望的方式设置参数。这就是 NSInvocation 和 Objective-C 运行时的其他各种胆量所做的,因为在 C 中,参数必须布置在某些寄存器中,以某种方式溢出到堆栈上。这会根据平台(x86、x64、ARM)而有所不同。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 2015-12-22
        • 1970-01-01
        • 2017-06-23
        • 1970-01-01
        • 2018-08-15
        • 2013-08-22
        • 1970-01-01
        • 1970-01-01
        • 2011-07-09
        相关资源
        最近更新 更多