UPDATE 2010-09-04: I wrote this post about an older version of Apple’s singleton example. The current version is much better: It still doesn’t handle over-releases the way I would like, but there’s nothing objectively wrong with it. As such, there’s nothing wrong with using Apple’s current example. You can continue reading this post to see my criticism of Apple’s old singleton example and my own implementation.
A singleton is a simple concept: It’s an object that you can only have one of. Being that simple, you may think it’s hard to get wrong.
If you do, I welcome you to the wonderful world of programming.
The Cocoa Fundamentals Guide is necessary reading for all Cocoa programmers. Even so, nobody’s perfect, and the CFG’s authors are no exception.
On Twitter last night, a mistake of theirs came to light when someone named Nick tweeted about how hard it is to make a singleton in Cocoa. Nick cited an example from the CFG; Chris Hanson responded that “that example does WAY too much work”, and Bill Bumgarner added that the example is “ridiculous”. They
‘re were both right.
The example, in order to “ensure singleton behavior”, overrides four basic Foundation methods:
In the override implementations,
autorelease are all no-ops.
release does exactly nothing; the other two do nothing but return
self. The override implementation of
retainCount unconditionally returns
Before I proceed any further, I’ll give you a caution about singletons. You generally do not need to make your object a singleton at all. Even if your application has a single-window interface, with only one of each controller, you probably don’t need to enforce that. Just don’t create more than one. Then, if you do create more than one, you have a bug.
Let’s digress for a moment. Imagine if you allowed it. Imagine that you allow, say, your app delegate to create more than one of your root controller object. Your app delegate will then proceed to set up both twin objects, and they both respond to such things as notifications.
Bad, right? Now let’s say you “fix” this by making your root controller a singleton.
Your app delegate is still trying to create two root controllers. It’s still trying to set up two root controllers, and as a result of the two set-up messages, the controller is signed up twice for notifications.
But now you really only have one root controller. Your app delegate creates the first one, then gets it again when it tries to create the second. All according to plan so far. Then your app delegate sets up the first object—and then it sets it up again, again thinking that it’s talking to a second object. Even worse, because the object is now signed up twice for notifications (once per set-up message), every notification response happens twice.
You now have only one root controller, but you didn’t fix the bug, which wasn’t in the controller at all, but in the app delegate. To fix the bug, you must fix the app delegate; you don’t need a singleton for this at all.
OK, digression over. Singletons are bad. Avoid them. If you have a lot of them, rip most of them out. (Dave Dribin has bookmarked a lot of other good cases against singletons, and BJ Homer points out that they aren’t all bad. More on BJ’s post later.)
Back to the singleton at hand.
First, let’s look at
retainCount. A comment explains that
UINT_MAX “denotes an object that cannot be released”, implying that the framework itself considers
UINT_MAX a special value.
This is actually correct, although I originally thought (and wrote, in an earlier version of this post) that it was bogus. The documentation for
retainCount explicitly says that objects whose
release method does nothing should return
Next on the hit list:
autorelease. This is just mostly pointless.
autorelease means nothing more than “send this a
release message later”. If
release does nothing, then
autorelease becomes “do nothing later”. All this override does is move the nothing up to now. A performance optimization, perhaps, but I’d say it’s premature.
And now we come to the real villains:
First off, you shouldn’t retain a singleton anyway. I can’t imagine why you would do this, except incidentally, by making it a timer target or something.
But even if you do think of a reason to retain a singleton, you still need to balance retain and release. If you retain without releasing afterward, you are under-releasing the object. If you release without retaining previously, you are over-releasing the object. These two statements have no exceptions.
If you break
release, then you’re able to over-retain or over-release (or even both!) the object with impunity. Your app no longer crashes, but you didn’t really fix the problem; you’re just getting away with it. You’re still trying to over-retain and/or over-release an object.
The Cocoa Fundamentals Guide’s primary audience is new Cocoa developers. Those who have never used a retain-counting system before may very well over-release the singleton, and Apple’s example singleton implementation hides that bug from them. That’s bad; every Cocoa programmer should know how to recognize an over-release crash, and breaking
release denies the reader an opportunity to learn that. I’ve filed a documentation bug report about the example.
Also, a rebuttal
BJ Homer responds at a different angle to last night’s flurry of tweets.
First, though, we need to get a definition straight. A singleton is a class of which there should only be one instance in any given process. There are actually very few singleton classes in the Cocoa framework including NSDocumentController and NSFontManager. You cannot create more than one of these objects; …
This is true. A singleton object is an object that your app can only have one of. There are a couple of different ways to do this:
… if you try to call
[[NSDocumentController alloc] init], you’ll get back the exact same object as you do when you call
[NSDocument sharedDocumentController], no matter how many times you call alloc and init.
NSApplicationis arguably a singleton as well; you can alloc another one, but you’ll get an assertion failure when you call init.
Another way is to simply implement the
sharedFramistan method, and leave
init alone. I suspect that this is common, since it’s the easiest way, but BJ is right that it isn’t a true singleton, since it allows multiple instances.
Where BJ goes wrong is in his defense of the
Consider the following example:
MyFloozit *floozit1 = [[MyFloozit alloc] init]; [floozit1 doSomething]; [floozit1 release]; MyFloozit *floozit2 = [[MyFloozit alloc] init]; // MyFloozit is a singleton, so this should be the same object as floozit1 [floozit2 doSomething]; // CRASH HERE [floozit2 release];
We’ll leave aside the problem that you probably should be using
sharedFloozit to obtain the singleton instance of
floozit1is set, a new
MyFloozitis allocated, and a static
MyFloozitpointer is set. When
floozit1is released, that static pointer is still pointing to the old instance. As a result, when we try to set
floozit2(or when anyone else tries to call
[MyFloozit sharedFloozit]), we get back a pointer to that same instance. The one that has been dealloc-ed.
BJ is missing something else the CFG says, which he even quoted later on:
Situations could arise where you want a singleton instance (created and controlled by the class factory method) but also have the ability to create other instances as needed through allocation and initialization. …
(Emphasis added by me.)
A singleton object owns itself. As such, it retains itself. As such, it should never die, because it always has at least one owner—and that’s without breaking
release. If it does die, it’s because something over-released it; later, something that was using it will crash, which lets you know that you have a bug. This is a good thing, because now you can fix the bug.
Each of the
release messages in BJ’s example balances an
alloc message above it. That
alloc message may actually return an existing object, but we’re expecting to own a new object. Therefore, the singleton’s
allocWithZone: should implicitly retain the existing object.
There is no good reason to override
release. Don’t do it. This also goes for
autorelease. And, since you never override
release, you also never need to override
Doing it right
So, having thoroughly dismantled the [old] Apple documentation’s poor implementation of a singleton, let’s look at the correct way to do it.
Let’s go through the requirements:
- The One True Instance is the only instance. (If you deliberately allow multiple instances, I call this a “multipleton”. I’ll leave that as an exercise for the reader, and concentrate on the true singleton here.)
- There is a
sharedFramistanmethod. It tests whether the One True Instance exists; if not, it creates the object and remembers it in file-scope
staticstorage. Then it returns the One True Instance.
- We’ll allow going through
init, and return the same instance. We’ll do this in
allocWithZone:, as Apple did. We’ll also need to make sure
initdoesn’t do its work twice on the same object.
[UPDATE 2010-09-04: I used to have the code inline in the post here; I have since moved it to a Mercurial repository on Bitbucket. You can download PRHEmptySingleton.h and .m from there.]
This is actually even more complex than Apple’s example, but it passes all six of these tests:
sharedFramistanalways returns the same object.
initalways produce the same object (which, itself, is the same object as
initwill not return an object confused by multiple
- Over-releasing causes a crash.
releasenever causes a crash.
[super init]returns a different object,
Apple’s [old] example fails test 4. BJ doesn’t show an implementation for his example, but according to his own description of its behavior, it fails test 5. My version passes all six.
Hiding bugs is bad. Even worse is giving code that can hide a bug to new Cocoa programmers who could really use practice in detecting and fixing that kind of bug.
If you really need to implement a singleton, there is a right way to do it. The way currently shown in the Cocoa Fundamentals Guide isn’t it.
Don’t change the behavior of
UPDATE 2009-09-04: Removed
hasInited instance variable after Christiaan Hofman pointed out its redundancy in the comments.
UPDATE 2009-09-19: Moved assignment of the
sharedInstance static variable from
-init, as suggested by Uli Kusterer.
UPDATE 2010-09-04: Replaced inline code sample with links to the Mercurial repository for the sample.