I’ll preface this post with the standard advice: Don’t create singletons if you don’t absolutely have to. In general, if you’re creating a global “manager” object of some sort, you’re doing something wrong.
That said, there’s still occasionally a reason to have such a global singleton, such as a “default something.” The sample code in the Cocoa Fundamentals Guide goes to a lot more trouble than it needs to in order to ensure that a class is a singleton.
This is almost never what you want.
First off, you probably want your class to be testable in a variety of configurations. In your unit tests, instead of getting your shared singleton instance in your -setUp
method and “resetting” its state in -tearDown
, you’d be better off just instantiating a new instance in -setUp
and releasing it in -tearDown
.
Also, the example in the Cocoa Fundamentals Guide does a lot of work that it simply doesn’t need to. This is all you really need to do to create a singleton in Cocoa:
@interface SomeManager : NSObject
+ (id)sharedManager;
@end
@implementation SomeManager
+ (id)sharedManager {
static id sharedManager = nil;
if (sharedManager == nil) {
sharedManager = [[self alloc] init];
}
return sharedManager;
}
@end
That’s it! The astute reader will notice, of course, that this isn’t thread-safe. I got rid of the @synchronized (self)
because it won’t do the right thing; depending on what actual class is sent +sharedManager
, the value of self
will be different!
For the sake of argument, though, let’s say that you do want a singleton with which you can interact from multiple threads at once. One way to do this would be to create your singleton instance in +initialize
since it will always be run, on a single thread, before any other methods in your class:
@implementation SomeManager
static id sharedManager = nil;
+ (void)initialize {
if (self == [SomeManager class]) {
sharedManager = [[self alloc] init];
}
}
+ (id)sharedManager {
return sharedManager;
}
@end
By doing this, you avoid the performance bottleneck of @synchronized
taking a recursive lock every time +sharedManager
is invoked.
If you want to get fancier, and it’s OK to temporarily have more than one instance of your singleton created, you could even use objc_atomicCompareAndSwapGlobalBarrier
to assign the value to return from +sharedManager
, though this is probably also more work than it’s worth; after all, +initialize
will only be invoked once for your class. (Though it can be re-invoked as a side-effect of initializing subclasses, hence the if (self == [SomeManager class]) { }
idiom.)
In all of the above cases, you’ve done a whole lot less work than the example in the Cocoa Fundamentals Guide, and your code is a lot more likely to be correct as a result.
{ 17 } Comments