Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Objective-C singleton should implement init method?

I read a couple of amazing resources on singletons in Obj-C:

  1. SO question: What does your Objective-C singleton look like?
  2. Friday Q&A: Care and Feeding of Singletons
  3. 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.

like image 809
matm Avatar asked Sep 01 '11 17:09

matm


1 Answers

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.

like image 56
MechEthan Avatar answered Sep 19 '22 02:09

MechEthan