Is this the final ref counted implementation of Objective-C singleton?

Here is what I came up with after looking at linguistic literature. Did I forget something?

@implementation MySingleton static MySingleton *mySharedInstance = nil; //called by atexit on exit, to ensure all resources are freed properly (not just memory) static void singleton_remover() { //free resources here } + (MySingleton*) sharedInstance{ return mySharedInstance; } + (void)initialize { if (self == [MySingleton class]) { mySharedInstance = [[super allocWithZone:NULL] init]; atexit( singleton_remover ); } } + (id)allocWithZone:(NSZone *)zone { return [self sharedInstance]; } - (id)copyWithZone:(NSZone *)zone { return self; } - (id)retain { return self; } - (NSUInteger)retainCount { return NSUIntegerMax; //denotes an object that cannot be released } - (void)release { //do nothing } - (id)autorelease { return self; } 
+6
objective-c iphone singleton atexit
source share
5 answers

which avoids blocking time synchronization

If you want your software to be reliable, avoid designs that work "most of the time"

http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html

Table 4.1. Double lock

Double Check Lock - An attempt to reduce the overhead of a lock by checking the lock criteria before committing. Since double-checked locks are potentially dangerous, the system does not provide them with explicit support, and their use is not recommended.

+2
source share

A few suggestions (more for Mac Cocoa than iPhone, but may be useful to other people looking for the objective-c tag):

  • Don't worry about -allocWithZone: NULL, just -alloc will be fine.
  • Consider using dispatch_once () or pthread_once () where available
  • Using atexit is smart, but it may not be compatible with the quick shutdown of the application (not sure if this applies to the iPhone), since it effectively kills the application

Another funny picture:

 + (Foo *)sharedFoo { static Foo *sharedInstance = NULL; if (!sharedInstance) { Foo *temp = [[Foo alloc] init]; //NOTE: This MUST NOT have side effects for it to be threadsafe if (!OSAtomicCompareAndSwapPtrBarrier(NULL, temp, &sharedInstance)) { [temp release]; } } return sharedInstance; } 
+1
source share

The singleton_remover function will not do anything because you redefined release to do nothing. Your allocWithZone: method also does the same no-op when it sends retain to the shared instance (and completely ignores the allocation in the specified zone). Perhaps you should have a flag that toggles whether your shared instance is invincible (i.e., untranslatable) or not.

In any case, the OS will still clear all memory. The documentation states that the operating system quickly restores all memory at once than for your application, in order to slowly return it in parts.

If your shared instance manages resources that you always need to clean up when your application terminates, you must register it to receive the UIApplicationWillTerminateNotification and perform the cleanup there.

 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(performCleanup:) name:UIApplicationWillTerminateNotification object:nil]; 
0
source share

EDIT

I include this at the top, below you can see my historical background questions and implementation. However, I think I have found the best way to provide a sharedInstance method without the overhead of locking, I would like to hear potential concerns about this:

 // Volatile to make sure we are not foiled by CPU caches static volatile ALBackendRequestManager *sharedInstance; // There no need to call this directly, as method swizzling in sharedInstance // means this will get called after the singleton is initialized. + (MySingleton *)simpleSharedInstance { return (MySingleton *)sharedInstance; } + (MySingleton*)sharedInstance { @synchronized(self) { if (sharedInstance == nil) { sharedInstance = [[MySingleton alloc] init]; // Replace expensive thread-safe method // with the simpler one that just returns the allocated instance. SEL orig = @selector(sharedInstance); SEL new = @selector(simpleSharedInstance); Method origMethod = class_getClassMethod(self, orig); Method newMethod = class_getClassMethod(self, new); method_exchangeImplementations(origMethod, newMethod); } } return (MySingleton *)sharedInstance; } 

And the historical discussion around initialization:

Now I see that the source code was more like mine (below), except for checking the instance out of lock.

Although the new initialization method + (void) is interesting, I'm not sure if I like it better. It seems that now, to get a singleton instance, you should now always call:

 MySingleton instance = [[MySingleton alloc] init]; 

It is not right? Is this weird and more effective if the call to initialize is already blocked for you? The double blocking method seems to work fine for this use case, and also avoids blocking (at the potential cost of double placement, I think, since more than one thread might fail through if).

Another thing that seems strange is if the initialize method was really preferable, why don't we see it elsewhere? Objective-C has existed for a long time, and I fear the underlying mechanisms that differ from almost all published examples.

My code that I am currently using (which reflects what I saw elsewhere, including this answer):

 + (MySingleton *)sharedInstance { @synchronized(self) { if (sharedInstance == nil) sharedInstance = [[MySingleton alloc] init]; } return sharedInstance; } + (id)allocWithZone:(NSZone *)zone { @synchronized(self) { if (sharedInstance == nil) { sharedInstance = [super allocWithZone:zone]; return sharedInstance; // assignment and return on first allocation } } return nil; // on subsequent allocation attempts return nil } 
0
source share

Your implementation is thread-safe and seems to cover all databases (+ initialization is sent thread-safe at runtime)

edit: a lot of code will not be safe to call during the atexit function. Registration for UIApplicationWillTerminateNotification in the main thread is safer.

edit2: I reworked and refined the template that I use in the macro. -init is called once upon the first call of +sharedInstance and -dealloc is called when the application terminates.

 #define IMPLEMENT_UIAPP_SINGLETON(class_name) \ static class_name *shared ## class_name; \ + (void)cleanupFromTerminate \ { \ class_name *temp = shared ## class_name; \ shared ## class_name = nil; \ [temp dealloc]; \ } \ + (void)registerForCleanup \ { \ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanupFromTerminate) name:UIApplicationWillTerminateNotification object:nil]; \ } \ + (void)initialize { \ if (self == [class_name class]) { \ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; \ if ([NSThread isMainThread]) \ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanupFromTerminate) name:UIApplicationWillTerminateNotification object:nil]; \ else \ [self performSelectorOnMainThread:@selector(registerForCleanup) withObject:nil waitUntilDone:NO]; \ shared ## class_name = [[super allocWithZone:NULL] init]; \ [pool drain]; \ } \ } \ + (class_name *)sharedInstance \ { \ return shared ## class_name; \ } \ + (id)allocWithZone:(NSZone *)zone \ { \ return shared ## class_name; \ } \ - (id)copyWithZone:(NSZone *)zone \ { \ return self; \ } \ - (id)retain \ { \ return self; \ } \ - (NSUInteger)retainCount \ { \ return NSUIntegerMax; \ } \ - (void)release \ { \ } \ - (id)autorelease \ { \ return self; \ } 
0
source share

All Articles