Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get runtime Block type metadata in Objective-c?

I'm writing a class where you register an object and a property to observe. When the property gets set to something non-nil, a registered callback selector is called (like target-action). The selector may have three different signatures, and the right one is called depending on which type was registered.

This works fine, but now I want to add the ability to register a Block instead of a selector as the "callback function". Is it possible to find out the function signature of the supplied Block and handle the callback differently depending on the type of Block supplied?

For example:

- (void)registerCallbackBlock:(id)block
{
    if ([self isBlock:block] {
        if ([self isMethodSignatureOne:block]) { /* */ }
        else if ([self isMethodSignatureTwo:block]) { /* */ }
        else { assert(false); }  // bad Block signature

        block_ = block;  // assuming ARC code
    }
    else { assert(false); } // not a block
} 

- (void)callBlock 
{
    if ([self isMethodSignatureOne:block_] {
        block_(arg1_, arg2_);         // needs casting?
    }
    else if ([self isMethodSignatureTwo:block_) {
        block_(arg1_, arg2_, arg3_);  // needs casting?
    }
}

Any ideas?

I know I can make different register functions with specific typedef'ed Block arguments but I would rather have a single function, if possible.

like image 661
Påhl Melin Avatar asked Jul 08 '11 12:07

Påhl Melin


3 Answers

If you're compiling with clang, you can get this information.

From the Clang block ABI spec:

The ABI of blocks consist of their layout and the runtime functions required by the compiler. A Block consists of a structure of the following form:

struct Block_literal_1 {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 {
        unsigned long int reserved; // NULL
        unsigned long int size;     // sizeof(struct Block_literal_1)
        // optional helper functions
        void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
        void (*dispose_helper)(void *src);             // IFF (1<<25)
        // required ABI.2010.3.16
        const char *signature;                         // IFF (1<<30)
    } *descriptor;
    // imported variables
};

The following flags bits are in use thusly for a possible ABI.2010.3.16:

enum {
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
    BLOCK_HAS_CTOR =          (1 << 26), // helpers have C++ code
    BLOCK_IS_GLOBAL =         (1 << 28),
    BLOCK_HAS_STRET =         (1 << 29), // IFF BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE =     (1 << 30), 
};

In practice, the current version of clang does encode signature information. According to a comment here, the format is just a standard objective-c method encoding string. That said, the signature format isn't documented (as fas as I can tell), so I'm not sure how much you can assume it won't break with compiler updates.

like image 136
Chris Devereux Avatar answered Nov 15 '22 18:11

Chris Devereux


I don't believe this is actually possible with the current ABI. However, you could do something like this:

- (void)registerSimpleCallback: (BlockWith2Args)block {
    BlockWith3Args wrapper = ^ (id arg1, id arg2, id arg3) {
        block(a, b);
    };
    [self registerComplexCallback: wrapper];
}

- (void)registerComplexCallback: (BlockWith3Args)block {
    [block_ release];
    block_ = [block copy];
}

- (void)callBlock {
    if (block_)
        block_(arg1, arg2, arg3);
}
like image 40
Jonathan Grynspan Avatar answered Nov 15 '22 16:11

Jonathan Grynspan


Yes we can, as I answered in another question.

The block struct is a published clang standard, and AFAIK, can be used all you want.

like image 1
Clay Bridges Avatar answered Nov 15 '22 18:11

Clay Bridges