Checking Objective-C block type?

adib picture adib · Jan 28, 2012 · Viewed 8.3k times · Source

This is primarily a curiosity, I'm not really sure what's the practical use of this but here goes.

Since blocks are also Objective-C objects, is it possible to check their type? That is, does it respond to the isKindOfClass: message and how to use that message with respect to blocks?

My naive thought that it's probably like this:

-(void) aMethod {
    typedef int (^BlockA)(int x, int y);
    id blockVar = ...; // get a block from somewhere
    if([blockVar isKindOfClass:BlockA]) {
        BlockA blockVarA = blockVar;
        int result = blockVarA(1,2);
    }
}

The code above probably won't work. But if it is possible to check a block's type, what is the correct way to do it?

Answer

Clay Bridges picture Clay Bridges · Jun 8, 2012

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.