Created
October 21, 2011 02:38
-
-
Save mikeash/1302977 to your computer and use it in GitHub Desktop.
NSInvocation works for blocks too!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <dlfcn.h> | |
#import <Foundation/Foundation.h> | |
struct BlockDescriptor | |
{ | |
unsigned long reserved; | |
unsigned long size; | |
void *rest[1]; | |
}; | |
struct Block | |
{ | |
void *isa; | |
int flags; | |
int reserved; | |
void *invoke; | |
struct BlockDescriptor *descriptor; | |
}; | |
static void *BlockImpl(id block) | |
{ | |
return ((struct Block *)block)->invoke; | |
} | |
static const char *BlockSig(id blockObj) | |
{ | |
struct Block *block = (void *)blockObj; | |
struct BlockDescriptor *descriptor = block->descriptor; | |
int copyDisposeFlag = 1 << 25; | |
int signatureFlag = 1 << 30; | |
assert(block->flags & signatureFlag); | |
int index = 0; | |
if(block->flags & copyDisposeFlag) | |
index += 2; | |
return descriptor->rest[index]; | |
} | |
@interface NSInvocation (PrivateHack) | |
- (void)invokeUsingIMP: (IMP)imp; | |
@end | |
typedef void (^BlockInterposer)(NSInvocation *inv, void (^call)(void)); | |
@interface FakeBlock : NSObject | |
{ | |
int _flags; | |
int _reserved; | |
IMP _invoke; | |
struct BlockDescriptor *_descriptor; | |
id _forwardingBlock; | |
BlockInterposer _interposer; | |
} | |
- (id)initWithBlock: (id)block interposer: (BlockInterposer)interposer; | |
@end | |
@implementation FakeBlock | |
- (id)initWithBlock: (id)block interposer: (BlockInterposer)interposer | |
{ | |
if((self = [super init])) | |
{ | |
_forwardingBlock = [block copy]; | |
_interposer = [interposer copy]; | |
_invoke = [self methodForSelector: @selector(thisDoesNotExistOrAtLeastItReallyShouldnt)]; | |
} | |
return self; | |
} | |
- (void)dealloc | |
{ | |
[_forwardingBlock release]; | |
[_interposer release]; | |
[super dealloc]; | |
} | |
- (NSMethodSignature *)methodSignatureForSelector: (SEL)sel | |
{ | |
const char *types = BlockSig(_forwardingBlock); | |
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes: types]; | |
while([sig numberOfArguments] < 2) | |
{ | |
types = [[NSString stringWithFormat: @"%s%s", types, @encode(void *)] UTF8String]; | |
sig = [NSMethodSignature signatureWithObjCTypes: types]; | |
} | |
return sig; | |
} | |
- (void)forwardInvocation: (NSInvocation *)inv | |
{ | |
[inv setTarget: _forwardingBlock]; | |
_interposer(inv, ^{ | |
[inv invokeUsingIMP: BlockImpl(_forwardingBlock)]; | |
}); | |
} | |
@end | |
id ForwardingBlock(BlockInterposer interposer, id block) | |
{ | |
return [[[FakeBlock alloc] initWithBlock: block interposer: interposer] autorelease]; | |
} | |
int main(int argc, char **argv) | |
{ | |
@autoreleasepool | |
{ | |
void (^block)(int) = ForwardingBlock(^(NSInvocation *inv, void (^call)(void)) { | |
[inv setArgument: &(int){ 4242 } atIndex: 1]; | |
call(); | |
}, ^(int testarg){ | |
NSLog(@"%d %d", argc, testarg); | |
}); | |
block(42); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment