Checking Objective-C block type?
Can do, kinda sorta.
But first, let's disambiguate. -[NSObject isKindOfClass:]
can tell you it's a block, and that's about it. E.g. I believe this line of code -- ostensibly & unfortunately A BAD IDEA -- will return YES for blocks on present Lion & iOS 5.x:
[myBlock isKindOfClass:NSClassFromString(@"NSBlock")]
That won't help you distinguish the block's function signature.
But it can be done, by snagging the signature from the block's documented internal struct. Code follows for an example OS X command-line app, much of which ripped from Mike Ash's MABlockClosure (great detailed explanation). (UPDATE: Github project CTObjectiveCRuntimeAdditions also apparently provides library code for just this purpose.)
#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 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];
}
int main(int argc, const char * argv[])
{
@autoreleasepool {
int (^block)(NSNumber *) = ^(NSNumber *num) {
NSLog(@"%@ %@", NSStringFromClass([num class]), num);
return [num intValue];
};
NSLog(@"signature %s", BlockSig(block));
NSLog(@"retval %d", (int)block([NSNumber numberWithInt:42]));
}
return 0;
}
Run this and you should get something like:
[58003:403] signature i16@?0@8
[58003:403] __NSCFNumber 42
[58003:403] retval 42
The numbers in the signature (I'm told they are offsets) can be stripped for simpler i@?@
.
The signature is in the @encode format, which isn't perfect (e.g. most objects map to same @
), but should afford you some ability to distinguish blocks with different signatures at runtime.
While it's not documented in the Apple link, my testing points to @?
being the code for a block type, which makes sense of the signature above. I found a clang-developers discussion on this issue which seems to back this up.
The "BlockA
" in (^BlockA)
is the variable name (in this case a typedef), not its class.
Blocks are objects, but not regular subclasses of NSObject
. They only implement a subset of the methods. -isKindOfClass:
will probably just crash.
Blocks are of the type NSMallocBlock
or NSConcreteGlobalBlock
, ... depending on where they were created (heap, stack, ...).
It seems that blocks are of classes like __NSGlobalBlock__
, __NSStackBlock__
, or __NSMallocBlock__
, etc., whose inheritance chain eventually goes to NSBlock
and then NSObject
. So you could test whether something is a block by doing [... isKindOfClass:NSClassFromString(@"NSBlock")]
. However, there doesn't seem to be any way to query a block's signature (return type and argument types) at runtime, so you wouldn't be able to distinguish between blocks of different signatures.
As well as Apple having nothing I can find to say on the matter, poking at a block with class_copyMethodList
and method_getName
reveals no obvious exposed methods. So I'm going to say that it isn't possible to check their type.
A old question, but anyway:
If you want a simple way of doing this: (Compile it with -fno-objc-arc)
Class __NSGlobalBlock__CLASS () {
static Class result = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dispatch_block_t thisIsAGlobalBlock = ^{// a block with no variables will be a __NSGlobalBlock__
};
result = [[thisIsAGlobalBlock class] retain];
});
return result;
};
Class __NSStackBlock__CLASS () {
static Class result = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__block dispatch_block_t thisIsAStackBlock = ^{
return ;// we really DON'T want infinate recursion
thisIsAStackBlock();// including a reference to __block var makes this a __NSStackBlock__
};
result = [[thisIsAStackBlock class] retain];
});
return result;
};
Class __NSMallocBlock__CLASS () {
static Class result = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__block dispatch_block_t thisIsAMallocBlock = Block_copy(// << turns the __NSStackBlock__ Block into a __NSMallocBlock__
^{
return ;// we really DON'T want infinate recursion
thisIsAMallocBlock();// including a reference to __block var makes this a __NSStackBlock__
});
result = [[thisIsAMallocBlock class] retain];
Block_release(thisIsAMallocBlock);
});
return result;
};
Test Code:
@autoreleasepool {
__block dispatch_block_t iAmAGlobalBlock = ^{
};
__block dispatch_block_t iAmAStackBlock = ^{
return;
iAmAStackBlock();
};
dispatch_block_t iAmHeapBlock = Block_copy(iAmAStackBlock);
dispatch_block_t iAmNotAHeapBlock = Block_copy(iAmAGlobalBlock);
if ([iAmAGlobalBlock isKindOfClass:__NSGlobalBlock__CLASS()]) {
NSLog(@"very great success!");
}
if ([iAmAStackBlock isKindOfClass:__NSStackBlock__CLASS()]) {
NSLog(@"another great success!");
}
if ([iAmHeapBlock isKindOfClass:__NSMallocBlock__CLASS()]) {
NSLog(@"also great success!");
}
if ([iAmNotAHeapBlock isKindOfClass:__NSGlobalBlock__CLASS()]) {
NSLog(@"yet another great success!");
}
NSLog (@"Block classes, as reported by NSStringFromClass():\n__NSGlobalBlock__CLASS() = %@\n__NSStackBlock__CLASS() = %@\n__NSMallocBlock__CLASS() = %@\n[iAmAGlobalBlock class] = %@\n[iAmAStackBlock class] = %@\n[iAmHeapBlock class] = %@\n[iAmNotAHeapBlock class] = %@\n",
NSStringFromClass(__NSGlobalBlock__CLASS()),
NSStringFromClass(__NSStackBlock__CLASS()),
NSStringFromClass(__NSMallocBlock__CLASS()),
NSStringFromClass([iAmAGlobalBlock class]),
NSStringFromClass([iAmAStackBlock class]),
NSStringFromClass([iAmHeapBlock class]),
NSStringFromClass([iAmNotAHeapBlock class])
);
Block_release(iAmHeapBlock);
Block_release(iAmNotAHeapBlock);// not really needed, but since we did "Block_copy" it...
}