检查Objective-C块类型?

时间:2012-01-28 20:24:35

标签: objective-c oop closures objective-c-blocks introspection

这主要是好奇心,我不确定这个实际用途是什么,但是这里有。

由于块也是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 个答案:

答案 0 :(得分:42)

可以做,有点儿。

但首先,让我们消除歧义。 -[NSObject isKindOfClass:]可以告诉你这是一个障碍,就是这样。例如。我相信这个代码行 - 表面上和&不幸的是, A BAD IDEA - 对于现在的Lion& iOS 5.x:

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

这无助于区分块的功能签名。

但是可以通过从块的文档内部结构中获取签名来完成。代码遵循一个示例OS X命令行应用程序,其中大部分都是从Mike Ash的MABlockClosure(伟大的detailed explanation)中删除的。 (更新: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链接中记录,但我的测试指向@?是块类型的代码,这可以理解上面的签名。我在这个问题上找到了一个铿锵的开发者discussion,似乎支持这个。

答案 1 :(得分:11)

BlockA中的“(^BlockA)”是变量名称(在本例中为typedef),而不是其类。
块是对象,但不是NSObject的常规子类。它们只实现方法的一个子集。 -isKindOfClass:可能会崩溃 块的类型为NSMallocBlockNSConcreteGlobalBlock,...取决于它们的创建位置(堆,堆栈,......)。

答案 2 :(得分:8)

似乎块是__NSGlobalBlock____NSStackBlock____NSMallocBlock__等类,其继承链最终转到NSBlock然后{{1} }。因此,可以通过执行NSObject来测试是否存在某个块。但是,似乎没有任何方法可以在运行时查询块的签名(返回类型和参数类型),因此您将无法区分不同签名的块。

答案 3 :(得分:3)

除了苹果公司没有任何我可以在这个问题上发表的言论之外,用class_copyMethodListmethod_getName来阻止一个块显示没有明显暴露的方法。所以我要说的是检查它们的类型是不可能的。

答案 4 :(得分:1)

一个老问题,但无论如何:

如果你想要一个简单的方法:(用-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...

}