Как получить метаданные типа блока во время выполнения в Objective-c?

Я пишу класс, в котором вы регистрируете объект и свойство для наблюдения. Когда свойству присваивается значение, отличное от nil, вызывается зарегистрированный селектор обратного вызова (например, target-action). Селектор может иметь три разных подписи, и правильная вызывается в зависимости от того, какой тип был зарегистрирован.

Это прекрасно работает, но теперь я хочу добавить возможность регистрации блока вместо селектора в качестве «функции обратного вызова». Можно ли узнать сигнатуру функции предоставленного блока и по-разному обрабатывать обратный вызов в зависимости от типа предоставленного блока?

Например:

- (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?
    }
}

Любые идеи?

Я знаю, что могу создавать разные регистровые функции с конкретными аргументами typedefed Block, но я бы предпочел одну функцию, если это возможно.


person Påhl Melin    schedule 08.07.2011    source источник
comment
вы можете просто притвориться, что у него 3 аргумента, даже если на самом деле он принимает 1 или 2, потому что дополнительные аргументы будут просто проигнорированы   -  person user102008    schedule 09.06.2012


Ответы (3)


Если вы компилируете с clang, вы можете получить эту информацию.

Из спецификации ABI блока Clang:

ABI блоков состоит из их макета и функций времени выполнения, необходимых компилятору. Блок состоит из структуры следующего вида:

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
};

Таким образом, для возможного 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), 
};

На практике текущая версия clang действительно кодирует информацию о подписи. Согласно комментарию здесь, формат представляет собой просто стандартную строку кодирования метода Objective-C. Тем не менее, формат подписи не задокументирован (насколько я могу судить), поэтому я не уверен, насколько вы можете предположить, что он не сломается с обновлениями компилятора.

person Chris Devereux    schedule 11.10.2011
comment
Большое спасибо за подробную информацию! Итак, информация есть, но официально ее использование не разрешено. На практике это означает, что мне придется придерживаться более грубого API с несколькими методами регистрации. Это все еще работает, но я надеюсь, что Apple задокументирует эту информацию в будущем и/или опубликует новый API для получения информации о подписи блока официальным способом. - person Påhl Melin; 17.10.2011

Я не верю, что это действительно возможно с текущим ABI. Однако вы можете сделать что-то вроде этого:

- (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);
}
person Jonathan Grynspan    schedule 11.10.2011
comment
Спасибо за хорошую идею использовать блок-оболочку для обработки необязательных аргументов. В моем случае расширения для двух зарегистрированных методов обратного вызова сильно различаются, и использовать этот метод было бы невозможно. И мне жаль, что я дал принятый ответ Крису Деверо — вы так же правы, как и он, но я был очарован его подробной информацией о Clang. - person Påhl Melin; 17.10.2011

Да, мы можем, как я ответил в другом вопрос.

Структура блока является опубликованным стандартом clang, и, насколько мне известно, ее можно использовать как угодно.

person Clay Bridges    schedule 08.06.2012