7

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.

jscs
  • 63,694
  • 13
  • 151
  • 195
Påhl Melin
  • 657
  • 1
  • 7
  • 14
  • you might be able to just pretend it had 3 arguments, even if it really takes 1 or 2, because extra arguments will just be ignored – user102008 Jun 09 '12 at 08:34

3 Answers3

5

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.

Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
Chris Devereux
  • 5,453
  • 1
  • 26
  • 32
  • Thanks very much for the detailed information! So, the information is there but is not officially sanctioned to use. In practice that means I will have to stick to a more crude API with multiple registration methods. It still works, but I hope Apple will document this information in the future and/or publish a new API for getting the block signature information the "official way". – Påhl Melin Oct 17 '11 at 05:59
3

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);
}
Jonathan Grynspan
  • 43,286
  • 8
  • 74
  • 104
  • 1
    Thanks for the nice idea to use a wrapper block to handle optional arguments. In my case the augments are quite different for the two registered callback methods and it wouldn't be possible to use this method. And I'm sorry I gave the accepted answer to Chris Devereux – you are just as right as him but I was charmed by his detailed Clang info. – Påhl Melin Oct 17 '11 at 06:07
1

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.

Community
  • 1
  • 1
Clay Bridges
  • 11,602
  • 10
  • 68
  • 118