Singletons in Cocoa/Objective-C

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

  1. Clark Cox
    June 17, 2009

    Another way to make a singleton, that is still lazily loaded, thread-safe and only incurs the cost of synchronization once (i.e. it’s like a double-checked lock, except that it actually works):

    @implementation Foo
    
    static id sharedInstance;
    
    +(id)sharedInstance {
        @synchronized(self) {
            if(!sharedInstance) {
                sharedInstance = [[self alloc] init];
                class_replaceMethod(object_getClass(self),
                    @selector(sharedInstance),
                    [self methodForSelector: @selector(sharedInstance2)],
                    "@@:");
            }
        }
    
        return sharedInstance;
    }
    
    +(id)sharedInstance2 {
        return sharedInstance;
    }
    
    @end
    
    Reply
  2. Clark Cox
    June 17, 2009

    (Of course, that doesn’t address the subclassing issue that you mentioned)

    Reply
  3. eschaton
    June 17, 2009

    Yeah; using @synchronized (self) in a class method that could be invoked on a subclass is risky.

    I reformatted your code into Markdown, since it didn’t survive the HTML editor widget thingy.

    Reply
  4. Mo Ayazifar
    June 29, 2009

    Then finally which one is gonna be the best one regarding to performance? and how are we supposed to use them form outside? is there any sample code available for that?

    Reply
  5. eschaton
    July 6, 2009

    You really shouldn’t need to be worried about performance of singleton objects.

    Reply
  6. scott lewis
    September 27, 2009

    correct me if I’m wrong, but there seems to be a typo in the very first example as the static declaration is inside the class method, which means it will always be set to nil, and thus not really work as a “somewhat” singleton. Is that correct?

    Reply
  7. Kaelin
    October 6, 2009

    GCD now offers the possibility of doing this with dispatch_once(). This is almost certainly the most performant technique that’s both thread-safe and concise:

    • (id)sharedInstance;
      {
      static id sharedInstance = nil;
      static dispatch_once_t once = 0;
      dispatch_once(&once, ^ {
      sharedInstance = [[self alloc] init];
      });
      return sharedInstance;
      }
    Reply
  8. Dustin L. Howett
    January 23, 2010

    There’s actually no way for +initialize to be called with a self that isn’t the class +initialize belongs to, by the runtime. The runtime calls initialize in reverse order for the entire superclass tree (assuming that class has not already been initialized) and does not call “[super initialize].” +initialize is called once and only once for every class, as noted in the Apple documentation.
    The if(self == [SomeManager class]) guard is therefore unnecessary.

    Reply
  9. Dustin L. Howett
    January 23, 2010

    Amending my last comment: “Except for the case in which a subclass of yours does not provide +initialize.”

    Reply
  10. bangnoise
    February 1, 2010

    But your singleton isn’t a singleton at all – you can [[SomeManager alloc] init] as many instances as you like. The point of the Apple way which overrides allocWithZone: is to enforce singleton behaviour – if that’s not important to a singleton class, it maybe doesn’t need to be a singleton anyway.

    @synchronized([SomeManager class]) {
    }

    Is an improvement on Apple’s example locking, I think..?

    Reply
  11. Fred McCann
    May 21, 2010

    I’ve written up an in-depth discussion of the singleton pattern here:

    http://www.duckrowing.com/2010/05/21/using-the-singleton-pattern-in-objective-c/

    Reply
  12. Otto Schnurr
    September 28, 2010

    @scott: When a static variable is declared inside a function, it’s only initialized the first time the function is executed.

    Reply
  13. Charles Parnot
    August 19, 2011

    Is this thread-safe?

    @implementation Foo
    
    static id sharedInstance;
    
    +(id)sharedInstance
    {
        if (sharedInstance)
            return sharedInstance;
    
        @synchronized(self)
        {
            if (!sharedInstance)
                sharedInstance = [[self alloc] init];
        }
        return sharedInstance;
    }
    
    @end
    
    Reply
  14. Shea Ako
    October 21, 2011

    I’ve run into a situation that demonstrates that the example published here is not actually thread safe.

    Two threads call +sharedManager at (more or less) the same time. The earlier call to +sharedManager triggers +initialize on its thread, but before +initialize completes the second thread executes +sharedManager.

    When +sharedManager is called on the second thread it can return nil if +initialize hasn’t finished yet on the first thread.

    Using some sort of locking is necessary for thread safety.

    I use a lock in +sharedManager.around

       if (sharedManager == nil) {...}
    
    Reply
  15. Shea Ako
    October 21, 2011

    addendum to my previous comment – I meant to say,

    I use the first method described in the article and put a lock around:

       if (sharedManager == nil) {...}
    
    Reply
  16. Carlo
    December 15, 2011

    This is very very interesting. I’m in the situation that I want to create a global “manager” to manage Core Data (all the methods to access Core Data in the delegate make me really uncomfortable).
    Now I’m wondering: what is the right thing to do if singleton is bad?

    Reply
  17. Josephdiusy
    March 25, 2019

    epdl yxd tracking

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *