Подтвердить что ты не робот

Проверка типа блока Objective-C?

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

Так как блоки также являются объектами Objective-C, можно ли проверить их тип? То есть отвечает ли оно на сообщение isKindOfClass: и как использовать это сообщение в отношении блоков?

Моя наивная мысль, что это, вероятно, так:

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

Возможно, код выше не работает. Но если можно проверить тип блока, каков правильный способ сделать это?

4b9b3361

Ответ 1

Может делать, sorta sorta.

Но сначала позвольте однозначно устранить. -[NSObject isKindOfClass:] может рассказать вам об этом блоке, и об этом. Например. Я считаю, что эта строка кода - якобы и, к сожалению, A BAD IDEA - вернет YES для блоков на существующих Lion и iOS 5.x:

[myBlock isKindOfClass:NSClassFromString(@"NSBlock")]

Это не поможет вам отличить подпись функции блока.

Но это можно сделать, зацепив подпись из блока, документированного внутренней структурой. Код следует за примером приложения командной строки OS X, большая часть которого разорвана с Mike Ash MABlockClosure (отличное подробное объяснение). (UPDATE: проект Github CTObjectiveCRuntimeAdditions также, по-видимому, предоставляет библиотечный код только для этой цели.)

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

Запустите это, и вы должны получить что-то вроде:

[58003:403] signature [email protected][email protected]
[58003:403] __NSCFNumber 42
[58003:403] retval 42

Цифры в сигнатуре (мне говорят, что они являются смещениями) могут быть удалены для более простого [email protected][email protected].

Подпись находится в формате @encode, что не идеально (например, большинство объектов сопоставляется с тем же @), но должно предоставить вам некоторую способность различать блоки с разными сигнатурами во время выполнения.

Пока он не задокументирован в ссылке Apple, мой тест указывает на @?, являющийся кодом для типа блока, который имеет смысл вышеприведенной сигнатуры. Я нашел clang-developers обсуждение по этой проблеме, которая, похоже, подтверждает это.

Ответ 2

"BlockA" в (^BlockA) - это имя переменной (в данном случае typedef), а не его класс.
Блоки - это объекты, но не регулярные подклассы NSObject. Они реализуют только подмножество методов. -isKindOfClass:, вероятно, просто сбой.
Блоки имеют тип NSMallocBlock или NSConcreteGlobalBlock,... в зависимости от того, где они были созданы (куча, стек,...).

Ответ 3

Кажется, что блоки имеют такие классы, как __NSGlobalBlock__, __NSStackBlock__ или __NSMallocBlock__ и т.д., чья цепочка наследования в конечном итоге переходит в NSBlock, а затем NSObject. Таким образом, вы можете проверить, есть ли что-то в блоке, выполнив [... isKindOfClass:NSClassFromString(@"NSBlock")]. Однако, как представляется, не существует способа запросить подпись блока (тип возврата и типы аргументов) во время выполнения, поэтому вы не сможете различать блоки разных подписей.

Ответ 4

Как и Apple, я ничего не могу сказать по этому поводу, выкапывая блок с class_copyMethodList и method_getName, не обнаруживает очевидных открытых методов. Поэтому я собираюсь сказать, что проверить их тип невозможно.

Ответ 5

Старый вопрос, но в любом случае:

Если вам нужен простой способ сделать это: (Скомпилируйте его с помощью -fno-objc-arc)

Class __NSGlobalBlock__CLASS () {
    static Class result = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        dispatch_block_t thisIsAGlobalBlock = ^{// a block with no variables will be a __NSGlobalBlock__
        };
        result = [[thisIsAGlobalBlock class] retain];
    });
    return result;
};

Class __NSStackBlock__CLASS () {
    static Class result = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        __block dispatch_block_t thisIsAStackBlock = ^{
            return ;// we really DON'T want infinate recursion
            thisIsAStackBlock();// including a reference to __block var makes this a __NSStackBlock__
        };
        result = [[thisIsAStackBlock class] retain];
    });
    return result;
};

Class __NSMallocBlock__CLASS () {
    static Class result = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        __block dispatch_block_t thisIsAMallocBlock = Block_copy(// << turns the __NSStackBlock__ Block into a __NSMallocBlock__
                                                                 ^{
            return ;// we really DON'T want infinate recursion
            thisIsAMallocBlock();// including a reference to __block var makes this a __NSStackBlock__
        });

        result = [[thisIsAMallocBlock class] retain];
        Block_release(thisIsAMallocBlock);
    });
    return result;
};

Тестовый код:

@autoreleasepool {

    __block dispatch_block_t iAmAGlobalBlock = ^{


    };


    __block dispatch_block_t iAmAStackBlock = ^{
        return;
        iAmAStackBlock();
    };


    dispatch_block_t iAmHeapBlock = Block_copy(iAmAStackBlock);
    dispatch_block_t iAmNotAHeapBlock = Block_copy(iAmAGlobalBlock);


    if ([iAmAGlobalBlock isKindOfClass:__NSGlobalBlock__CLASS()]) {

        NSLog(@"very great success!");
    }

    if ([iAmAStackBlock isKindOfClass:__NSStackBlock__CLASS()]) {

        NSLog(@"another great success!");
    }


    if ([iAmHeapBlock isKindOfClass:__NSMallocBlock__CLASS()]) {

        NSLog(@"also great success!");
    }


    if ([iAmNotAHeapBlock isKindOfClass:__NSGlobalBlock__CLASS()]) {

        NSLog(@"yet another great success!");
    }




    NSLog (@"Block classes, as reported by NSStringFromClass():\n__NSGlobalBlock__CLASS() = %@\n__NSStackBlock__CLASS()  = %@\n__NSMallocBlock__CLASS() = %@\n[iAmAGlobalBlock class]  = %@\n[iAmAStackBlock class]   = %@\n[iAmHeapBlock class]     = %@\n[iAmNotAHeapBlock class] = %@\n",

           NSStringFromClass(__NSGlobalBlock__CLASS()),
           NSStringFromClass(__NSStackBlock__CLASS()),
           NSStringFromClass(__NSMallocBlock__CLASS()),

           NSStringFromClass([iAmAGlobalBlock class]),
           NSStringFromClass([iAmAStackBlock  class]),
           NSStringFromClass([iAmHeapBlock    class]),
           NSStringFromClass([iAmNotAHeapBlock    class])


           );



    Block_release(iAmHeapBlock);
    Block_release(iAmNotAHeapBlock);// not really needed, but since we did "Block_copy" it...

}