问题
I have block of unknown type (as id
) and array of arguments that need to passed into that block. Arguments may be objects or numbers/structs boxed as NSNumber/NSValue. Block may also return an object, number or struct. This is a library code, and types of arguments are not known beforehand.
Assuming that I can can dynamically read signature from the block descriptor, is there a way to construct something like an NSInvocation to call a block?
回答1:
Surprisingly this works:
CGAffineTransform (^block)(id x, int y, CGSize z) = ^(id x, int y, CGSize z){
NSLog(@"%@,%d,%@", x, y, NSStringFromCGSize(z));
CGAffineTransform t = { 1, 2, 3, 4, 5, 6 };
return t;
};
NSMethodSignature* sign = [NSMethodSignature signatureWithObjCTypes:block_signature(block3)];
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:sign];
[invocation setTarget:block3];
void* x = (__bridge void*)@"Foo";
int y = 42;
CGSize z = { 320, 480 };
[invocation setArgument:&x atIndex:1];
[invocation setArgument:&y atIndex:2];
[invocation setArgument:&z atIndex:3];
[invocation invoke];
CGAffineTransform t;
[invocation getReturnValue:&t];
But on the other hand, this does not:
NSMethodSignature* sign = [self methodSignatureForSelector:@selector(class)];
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:sign];
[invocation setTarget:block];
[invocation setSelector:@selector(class)];
[invocation invoke];
Class k = nil;
[invocation getReturnValue:&k];
AFAICS from the disassembly, implementation of the [NSInvocation invoke]
checks for the class of the target, and if it is a block (subclass of NSBlock
) then it always calls block function, regardless of the signature.
Updated: Reported as rdar://25289979
回答2:
There is! I have a project on GitHub called WoolBlockInvocation which aims to mirror NSInvocation
's interface for use with Blocks.
It was actually created in response to In Objective-C/C, can you write a function that combines 2 blocks?, so the constructor + (instancetype)invocationWithBlocks:(NSArray *)blocks;
assumes that you want to use it with multiple Blocks, but it works perfectly well with a single Block.
It figures out the encoding signature for you via the WSSBlockSignature helper class,* and gives you setArgument:atIndex:, getReturnValue:, invoke, and other methods just like NSInvocation
's.
Under the hood, it uses libffi to do the work. This requires a certain amount of prep to handle structs: CGSize, CGPoint, and CGRect are accounted for, and others can be added as needed.
(Cave: I originally tested it on OS X, x86_64. It hasn't been checked out on any other platform.)
来源:https://stackoverflow.com/questions/36153926/calling-a-block-though-runtime-anything-similar-to-nsinvocation