Проверка типа блока Objective-C? - PullRequest
27 голосов
/ 29 января 2012

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

Поскольку блоки также являются объектами 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);
    }
}

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

Ответы [ 5 ]

42 голосов
/ 08 июня 2012

Можно сделать , своего рода сортировка.

Но сначала давайте устраним неоднозначность.-[NSObject isKindOfClass:] могу сказать, что это блок, и это все.Например, я считаю, эта строка кода - якобы и, к сожалению, ПЛОХАЯ ИДЕЯ - вернет ДА ​​для блоков на нынешнем Lion & iOS 5.x:

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

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

Но это можно сделать, поймав подпись из документированной внутренней структуры блока.Далее приведен код для примера приложения командной строки OS X, большая часть которого взята из MABlockClosure Майка Эша (великолепное подробное объяснение ).(ОБНОВЛЕНИЕ: проект 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 i16@?0@8
[58003:403] __NSCFNumber 42
[58003:403] retval 42

Числа в подписи (мне говорят, что они являются смещениями) можно упростить для упрощения i@?@.

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

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

11 голосов
/ 29 января 2012

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

8 голосов
/ 29 января 2012

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

3 голосов
/ 29 января 2012

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

1 голос
/ 23 ноября 2013

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

Если вы хотите простой способ сделать это: (скомпилируйте его с -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...

}
...