Reputation: 393
I was going through and replacing @synthesized(self) locks w/ this method
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);
}
which while a bit more verbose exhibits significantly better performance in non-contested cases
along with this little macro (excuse poor formatting, it's very simple). To allow the block to be declared after the initial nil check, looks to help LLVM keep the "already initialized" path extremely fast. That's the only one I care about.
#define ThreadsafeFastInit(theClassToInit, theVariableToStoreItIn, aVoidBlockToRunAfterInit) if( theVariableToStoreItIn == nil) { _ThreadsafeInitWithBlock(theClassToInit, (void *)&theVariableToStoreItIn, aVoidBlockToRunAfterInit); }
So initially implemented it using the commented out sections for objc_superAllocTyped (actually first using [theClassToInit allocWithZone:NULL], which was definitely the best approach :) ), which worked great until I realized that most of the singletons in the project had overridden allocWithZone to return the singleton method... infinite loop. So I figured using objc_msgSendSuper should sort it out quickly, but I get this error.
[51431:17c03] +[DataUtils allocWithZone:]: unrecognized selector sent to class 0x4f9584
The error doesn't seem to be related to the actual problem, as...
(lldb) po 0x4f9584
$1 = 5215620 DataUtils
(lldb) print (BOOL)[$1 respondsToSelector:@selector(allocWithZone:)]
(BOOL) $2 = YES
So I'm definitely missing something... I compared to assembly generated by a [super allocWithZone:NULL] method in an empty class... almost identical except for the functions called have different names (maybe just using different symbols, no idea, can't read it that well).
Any ideas? I can use class_getClassMethod on the superclass and call the IMP directly, but I'm trying to be reasonable in my abuse of the runtime :)
Upvotes: 2
Views: 3212
Reputation: 393
Alright, this wasn't actually that tricky once I recalled that the meta class contains all of the method information for a Class instance obtained via -[self class] or +[self] -> thanks http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html
This error occurred because I was asking the runtime to look up the method in NSObject's set of instance methods, which obviously doesn't contain allocWithZone: . The mistake in the error log presumably originated because the receiver was a metaclass instance, and Apple has their interns implement error logs.
so while with a normal instance method call via objc_msgSendSuper, you would pass a metaclass instance as objc_super.super_class, to invoke a class method, the metaclass itself is needed (everything is one level up).
Example, and a diagram that helped me understand this - (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 ) );
Then the message can be resolved correctly. Makes perfect sense now that I started paying attention :P
Anyways, now that I found my mistake I feel like I've leveled up my Objc runtime understanding. I was also able to fix an architectural mistake made two years ago by someone I never met without having to modifying and re-test dozens of classes across 3 projects and 2 static libraries (God I love Objective-C). Replacing the @synchronized construct with a simple function call also halved the compiled code size of those methods. As a bonus, all our singleton accessors are now (more) threadsafe, because the performance cost for doing so is now negligible. Methods which naively re-fetched the singleton object multiple times (or in loops) have seen a huge speedup now that they don't have to acquire and release a mutex multiple times per invocation. All in all I'm very happy it all worked as I'd hoped.
I made a "normal" Objective-C method for this on a category of NSObject, which will work for both instance and Class objects to allow you to invoke a superclass's implementation of a message externally. Warning: This is only for fun, or unit tests, or swizzled methods, or maybe a really cool game.
@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);
}
so
[self performSelector:@selector(dealloc) asClass:[self superclass]];
would be equivalent to
[super dealloc];
Carry on runtime explorers! Don't let the naysayers drag you into their land of handwaving and black magik boxes, it's hard to make uncompromisingly awesome programs there*.
*Please enjoy the Objective-C runtime responsibly. Consult with your QA team for any bugs lasting more than four hours.
Upvotes: 8