I read a couple of amazing resources on singletons in Obj-C:
- SO question: What does your Objective-C singleton look like?
- Friday Q&A: Care and Feeding of Singletons
- Apple docs: Creating a Singleton Instance
but none of these resources addressed init
method concept explicitly and while still being a novice to Obj-C I'm confused how should I implement it.
So far I know that having init
private is not possible in Obj-C as it does not offer true private methods... so it's possible that user can call [[MyClass alloc] init]
instead of using my [MyClass sharedInstance]
.
What are my other options? I believe I should also handle开发者_开发技巧 subclassing scenarios of my singleton.
Well, an easy way around the init
is to just not write one to have it call the default NSObject implementation (which only returns self
). Then, for your sharedInstance
function, define and call a private function that performs init-like work when you instantiate your singleton. (This avoids user accidentally re-initializing your singleton.)
However!!! The major problem is with alloc
being called by a user of your code! For this, I personally recommend Apple's route of overriding allocWithZone:
...
+ (id)allocWithZone:(NSZone *)zone
{
return [[self sharedInstance] retain];
}
This means the user will still get your singleton instance, and they can mistakenly use as if they allocated it, and safely release it once since this custom alloc performs a retain on the singleton. (Note: alloc
calls allocWithZone:
and does not need to be separately overridden.)
Hope that helps! Let me know if you want more info~
EDIT: Expanding answer to provide example and more details --
Taking Catfish_Man's answer into consideration, it's often not important to create a bulletproof singleton, and instead just write some sensible comments in your headers/documentation and put in an assert
.
However, in my case, I wanted a thread-safe lazy-load singleton--that is, it does not get allocated until it needs to be used, instead of being automatically allocated on app launch. After learning how to do that safely, I figured I may as well go all the way with it.
EDIT#2: I now use GCD's dispatch_once(...)
for a thread-safe approach of allocating a singleton object only once for lifetime of an application. See Apple Docs: GCD dispatch_once. I also still add allocWithZone:
override bit from Apple's old singleton example, and added a private init named singletonInit
to prevent it from accidentally being called multiple times:
//Hidden/Private initialization
-(void)singletonInit
{
//your init code goes here
}
static HSCloudManager * sharedInstance = nil;
+ (HSCloudManager *) sharedManager {
static dispatch_once_t dispatchOncePredicate = 0;
dispatch_once(&dispatchOncePredicate, ^{
sharedInstance = [[super allocWithZone:NULL] init];
[sharedInstance singletonInit];//Only place you should call singletonInit
});
return sharedInstance;
}
+ (id) allocWithZone:(NSZone *)zone {
//If coder misunderstands this is a singleton, behave properly with
// ref count +1 on alloc anyway, and still return singleton!
return [[HSCloudManager sharedManager] retain];
}
HSCloudManager
subclasses NSObject
, and does not override init
leaving only the default implementation in NSObject
, which as per Apple's documentation only returns self. This means [[HSCloudManager alloc] init]
is the same as [[[HSCloud Manager sharedManager] retain] self]
, making it safe for both confused users and multi-threaded applications as a lazy-loading singleton.
As for your concern about user's subclassing your singleton, I'd say just comment/document it clearly. Anyone blindly subclassing without reading up on the class is asking for pain!
EDIT#3: For ARC compatibility, just remove the retain portion from the allocWithZone:
override, but keep the override.
Honestly? The whole fad of writing bulletproof singleton classes seems pretty overblown to me. If you're seriously that concerned about it, just stick assert(sharedInstance == nil) in there before you assign to it the first time. That way it'll crash if someone uses it wrong, promptly letting them know they're an idiot.
The init
method should not be affected. It will be the same in a singleton class as in a regular class. You may want to override allocWithZone:
(which is being called by alloc
) to avoid creating more than one instance of your class.
To make the init/new methods unavailable for callers of your singleton class you could use the NS_UNAVAILABLE macro in your header file:
- (id)init NS_UNAVAILABLE;
+ (id)new NS_UNAVAILABLE;
+ (instancetype)sharedInstance;
精彩评论