检查Objective-C块类型?

adi*_*dib 27 oop closures introspection objective-c objective-c-blocks

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

由于块也是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);
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码可能不起作用.但是,如果它可以检查块的类型,什么是做了正确的方法是什么?

Cla*_*ges 42

可以做,有点儿.

但首先,让我们消除歧义.-[NSObject isKindOfClass:]可以告诉你这是一个块,这就是它.例如,我相信这一行代码 - 表面上和不幸的是A BAD IDEA - 将为当前Lion和iOS 5.x上的块返回YES:

[myBlock isKindOfClass:NSClassFromString(@"NSBlock")]
Run Code Online (Sandbox Code Playgroud)

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

但是可以通过从块的文档内部结构中剔除签名来完成它.代码遵循示例OS X命令行应用程序,其中大部分都是从Mike Ash的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;
}
Run Code Online (Sandbox Code Playgroud)

运行这个,你应该得到类似的东西:

[58003:403] signature i16@?0@8
[58003:403] __NSCFNumber 42
[58003:403] retval 42
Run Code Online (Sandbox Code Playgroud)

签名中的数字(我被告知它们是偏移的)可以被剥离以便更简单i@?@.

签名是在@encode格式,这是不完美的(例如,大多数对象映射到相同的@),而应该给予你一些在运行时来区分不同的签名块的能力.

虽然它没有在Apple链接中记录,但我的测试指出@?是块类型的代码,这可以理解上面的签名.我找到了一个关于这个问题的开发人员讨论,这似乎支持了这一点.


Fab*_*ser 11

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


use*_*008 8

看来块是像类__NSGlobalBlock__,__NSStackBlock____NSMallocBlock__等,其继承链最终去NSBlock,然后NSObject.所以你可以通过测试测试某些东西是否是块[... isKindOfClass:NSClassFromString(@"NSBlock")].但是,似乎没有任何方法可以在运行时查询块的签名(返回类型和参数类型),因此您将无法区分不同签名的块.