Correct Singleton Pattern Objective C (iOS)?
Solution 1
Keep it simple:
+(instancetype)sharedInstance
{
static dispatch_once_t pred;
static id sharedInstance = nil;
dispatch_once(&pred, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (void)dealloc
{
// implement -dealloc & remove abort() when refactoring for
// non-singleton use.
abort();
}
That is it. Overriding retain
, release
, retainCount
and the rest is just hiding bugs and adding a bunch of lines of unnecessary code. Every line of code is a bug waiting to happen. In reality, if you are causing dealloc
to be called on your shared instance, you have a very serious bug in your app. That bug should be fixed, not hidden.
This approach also lends itself to refactoring to support non-singleton usage modes. Pretty much every singleton that survives beyond a few releases will eventually be refactored into a non-singleton form. Some (like NSFileManager
) continue to support a singleton mode while also supporting arbitrary instantiation.
Note that the above also "just works" in ARC.
Solution 2
// See Mike Ash "Care and Feeding of Singletons"
// See Cocoa Samurai "Singletons: You're doing them wrong"
+(MySingleton *)singleton {
static dispatch_once_t pred;
static MySingleton *shared = nil;
dispatch_once(&pred, ^{
shared = [[MySingleton alloc] init];
shared.someIvar = @"blah";
});
return shared;
}
Be aware that dispatch_once is not reentrant, so calling itself from inside the dispatch_once block will deadlock the program.
Don't try to code defensively against yourself. If you are not coding a framework, treat your class as normal then stick the singleton idiom above. Think of the singleton idiom as a convenience method, not as a defining trait of your class. You want to treat your class as a normal class during unit testing, so it's OK to leave an accessible constructor.
Don't bother using allocWithZone:
- It ignores its argument and behaves exactly like
alloc
. Memory zones are no longer used in Objective-C soallocWithZone:
is only kept for compatibility with old code. - It doesn't work. You can't enforce singleton behavior in Objective-C because more instances can always be created using
NSAllocateObject()
andclass_createInstance()
.
A singleton factory method always returns one of these three types:
-
id
to indicate the return type is not fully known (case where you are building a class cluster). -
instancetype
to indicate that the type returned is an instance of the enclosing class. - The class name itself (
MySingleton
in the example) to keep it simple.
Since you tagged this iOS, an alternative to a singleton is saving the ivar to the app delegate and then using a convenience macro that you can redefine if you change your mind:
#define coreDataManager() \
((AppDelegate*)[[UIApplication sharedApplication] delegate]).coreDataManager
Solution 3
If you want to unit test your singleton you also have to make it so that you can replace it with a mock singleton and/or reset it to the normal one:
@implementation ArticleManager
static ArticleManager *_sharedInstance = nil;
static dispatch_once_t once_token = 0;
+(ArticleManager *)sharedInstance {
dispatch_once(&once_token, ^{
if (_sharedInstance == nil) {
_sharedInstance = [[ArticleManager alloc] init];
}
});
return _sharedInstance;
}
+(void)setSharedInstance:(ArticleManager *)instance {
once_token = 0; // resets the once_token so dispatch_once will run again
_sharedInstance = instance;
}
@end
blackjacx
I am a passionated iOS developer since 2009. To see the opn source repos I maintain please have a look at this gist
Updated on August 22, 2022Comments
-
blackjacx over 1 year
I found some information in the net to create a singleton class using GCD. Thats cool because it's thread-safe with very low overhead. Sadly I could not find complete solutions but only snippets of the sharedInstance method. So I made my own class using the trial and error method - and et voila - the following came out:
@implementation MySingleton // MARK: - // MARK: Singleton Pattern using GCD + (id)allocWithZone:(NSZone *)zone { return [[self sharedInstance] retain]; } - (id)copyWithZone:(NSZone *)zone { return self; } - (id)autorelease { return self; } - (oneway void)release { /* Singletons can't be released */ } - (void)dealloc { [super dealloc]; /* should never be called */ } - (id)retain { return self; } - (NSUInteger)retainCount { return NSUIntegerMax; /* That's soooo non-zero */ } + (MySingleton *)sharedInstance { static MySingleton * instance = nil; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ // --- call to super avoids a deadlock with the above allocWithZone instance = [[super allocWithZone:nil] init]; }); return instance; } // MARK: - // MARK: Initialization - (id)init { self = [super init]; if (self) { // Initialization code here. } return self; } @end
Please feel free to comment and tell me if I've missing something or doing something completely wrong ;)
Cheers Stefan