我正在通过这种方法取代@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,但我试图在滥用运行时时合理:)
答案 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团队。