Search code examples
objective-coopclosuresobjective-c-blocksintrospection

Checking Objective-C block type?


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?


Solution

  • 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.