我有一个变量,块接受一些参数。参数的确切数量及其类型可以有所不同。例如,它可以是块
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;
}
}
答案 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参数的块并让块对其自身进行排序。这是一种经过验证的简单方法。