使用objc_msgSendSuper来调用类方法

Ste*_*azy 2 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);
}
Run Code Online (Sandbox Code Playgroud)

虽然有点更详细,但在无争议案件中表现得更好

随着这个小宏(原谅糟糕的格式,这很简单).为了允许在初始nil检查之后声明块,看起来有助于LLVM保持"已经初始化"的路径非常快.这是我唯一关心的.

#define ThreadsafeFastInit(theClassToInit, theVariableToStoreItIn, aVoidBlockToRunAfterInit) if( theVariableToStoreItIn == nil) { _ThreadsafeInitWithBlock(theClassToInit, (void *)&theVariableToStoreItIn, aVoidBlockToRunAfterInit); }
Run Code Online (Sandbox Code Playgroud)

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

[51431:17c03] +[DataUtils allocWithZone:]: unrecognized selector sent to class 0x4f9584
Run Code Online (Sandbox Code Playgroud)

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

(lldb) po 0x4f9584

$1 = 5215620 DataUtils

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

(BOOL) $2 = YES
Run Code Online (Sandbox Code Playgroud)

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

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

Ste*_*azy 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 ) );
Run Code Online (Sandbox Code Playgroud)

然后可以正确解析消息.现在我开始注意了,这是完美的感觉: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);
}
Run Code Online (Sandbox Code Playgroud)

所以

[self performSelector:@selector(dealloc) asClass:[self superclass]];
Run Code Online (Sandbox Code Playgroud)

相当于

[super dealloc];
Run Code Online (Sandbox Code Playgroud)

继续运行时探索者!不要让反对者把你拖到他们的手工和黑魔法盒的土地上,很难在那里做出毫不妥协的精彩节目*.

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