2

我正在经历并用这种方法替换@synthesized(self) 锁

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,但我试图合理地滥用运行时:)

4

1 回答 1

7

好吧,当我回忆起元类包含通过 -[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 方法,该方法适用于实例和类对象,以允许您在外部调用超类的消息实现。警告:这只是为了好玩,或单元测试,或混合方法,或者可能是一个非常酷的游戏。

@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 团队。

于 2013-02-01T04:05:37.450 回答