使用objc_msgSendSuper来调用类方法

时间:2013-01-31 20:58:28

标签: objective-c objective-c-runtime

我正在通过这种方法取代@synthesized(自我)锁

void _ThreadsafeInit(Class theClassToInit, void *volatile *theVariableItLivesIn, void(^InitBlock)(void))
{
    //this is what super does :X
    struct objc_super mySuper = {
        .receiver = (id)theClassToInit,
        .super_class = class_getSuperclass(theClassToInit)
    };

id (*objc_superAllocTyped)(struct objc_super *, SEL, NSZone *) = (void *)&objc_msgSendSuper;
//    id (*objc_superAllocTyped)(id objc_super, SEL, NSZone *) = (void *)&objc_msgSend;

    do {
        id temp = [(*objc_superAllocTyped)(&mySuper /*theClassToInit*/, @selector(allocWithZone:), NULL) init];//get superclass in case alloc is blocked in this class;
        if(OSAtomicCompareAndSwapPtrBarrier(0x0, temp, theVariableItLivesIn)) { //atomic operation forces synchronization
            if( InitBlock != NULL ) {
                InitBlock(); //only the thread that succesfully set sharedInstance pointer gets here
            }
            break;
        }
        else
        {
            [temp release]; //any thread that fails to set sharedInstance needs to clean up after itself
        }
    } while (*theVariableItLivesIn == NULL);
}

虽然稍微冗长一点,但在无争议案件中表现出明显更好的表现

以及这个小宏(借口格式不佳,很简单)。为了允许在初始nil检查之后声明块,看起来有助于LLVM保持“已经初始化”的路径非常快。这是我唯一关心的。

#define ThreadsafeFastInit(theClassToInit, theVariableToStoreItIn, aVoidBlockToRunAfterInit) if( theVariableToStoreItIn == nil) { _ThreadsafeInitWithBlock(theClassToInit, (void *)&theVariableToStoreItIn, aVoidBlockToRunAfterInit); }

所以最初使用objc_superAllocTyped的注释部分实现它(实际上首先使用[theClassToInit allocWithZone:NULL],这绝对是最好的方法:)),这很有效,直到我意识到项目中的大多数单身人士都有重写allocWithZone以返回单例方法...无限循环。所以我认为使用objc_msgSendSuper应该快速排序,但是我得到了这个错误。

[51431:17c03] +[DataUtils allocWithZone:]: unrecognized selector sent to class 0x4f9584

该错误似乎与实际问题无关,因为......

(lldb) po 0x4f9584

$1 = 5215620 DataUtils

(lldb) print (BOOL)[$1 respondsToSelector:@selector(allocWithZone:)]

(BOOL) $2 = YES

所以我肯定错过了一些东西......我比较了一个空类中[super allocWithZone:NULL]方法生成的汇编...除了调用的函数有不同的名称(可能只是使用不同的符号)几乎相同,不知道,不能读得那么好)。

有什么想法吗?我可以在超类上使用class_getClassMethod并直接调用IMP,但我试图在滥用运行时时合理:)

1 个答案:

答案 0 :(得分:7)

好吧,一旦我回忆起meta类包含通过 - [self class]或+ [self] - >获得的Class实例的所有方法信息,这实际上并不那么棘手。谢谢http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html

发生此错误是因为我要求运行时在NSObject的实例方法集中查找方法,这显然不包含allocWithZone :.错误日志中的错误大概是因为接收者是元类实例,Apple让他们的实习生实现错误日志。

所以当通过objc_msgSendSuper调用普通的实例方法时,你会传递一个元类实例作为objc_super.super_class来调用一个类方法,需要元类本身(一切都是一级)。

示例和帮助我理解这一点的图表 - (http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html

struct objc_super mySuper;
mySuper.receiver = theClassToInit; //this is our receiver, no doubt about it
//either grab the super class and get its metaclass
mySuper.super_class = object_getClass( class_getSuperclass( theClassToInit ) );
//or grab the metaclass, and get its super class, this is the exact same object
mySuper.super_class = class_getSuperclass( object_getClass( theClassToInit ) );

然后可以正确解析消息。现在我开始注意了,这是完美的意义:P

无论如何,既然我发现了自己的错误,我觉得我已经把我的Objc运行时理解升级了。我还能够解决两年前我从未见过的人所犯的架构错误,而不必修改和重新测试3个项目和2个静态库中的数十个类(上帝,我喜欢Objective-C)。用简单的函数调用替换@synchronized构造也会使这些方法的编译代码大小减半。作为奖励,我们所有的单件访问器现在(更多)线程安全,因为这样做的性能成本现在可以忽略不计。多次(或循环)天真地重新获取单个对象的方法已经看到巨大的加速,因为它们不必每次调用多次获取和释放互斥锁。总而言之,我很高兴这一切都像我希望的那样奏效。

我在一个NSObject类上为此做了一个“普通”的Objective-C方法,它可以同时用于实例和Class对象,以允许您从外部调用超类的消息实现。警告:这仅用于娱乐,单元测试或混合方法,或者可能是非常酷的游戏。

@implementation NSObject (Convenience)

-(id)performSelector:(SEL)selector asClass:(Class)class
{
    struct objc_super mySuper = {
        .receiver = self,
        .super_class = class_isMetaClass(object_getClass(self)) //check if we are an instance or Class
                        ? object_getClass(class)                //if we are a Class, we need to send our metaclass (our Class's Class)
                        : class                                 //if we are an instance, we need to send our Class (which we already have)
    };

    id (*objc_superAllocTyped)(struct objc_super *, SEL) = (void *)&objc_msgSendSuper; //cast our pointer so the compiler can sort out the ABI
    return (*objc_superAllocTyped)(&mySuper, selector);
}

所以

[self performSelector:@selector(dealloc) asClass:[self superclass]];

等同于

[super dealloc];

继续运行时资源管理器!不要让反对者把你拖进他们的手工和黑色魔法盒的土地上,很难在那里做出毫不妥协的精彩节目*。

*请负责任地享受Objective-C运行时。如果任何持续时间超过四小时的错误,请咨询您的QA团队。