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:
retain
release
autorelease
retainCount
In the override implementations, retain
, release
, and autorelease
are all no-ops. release
does exactly nothing; the other two do nothing but return self
. The override implementation of retainCount
unconditionally returns UINT_MAX
.
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 UINT_MAX
.
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: retain
and release
.
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 retain
and 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 retain
and 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. NSApplication
is 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 allocWithZone:
and 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 retain
and release
overrides:
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 MyFloozit
.
When floozit1
is set, a new MyFloozit
is allocated, and a static MyFloozit
pointer is set. When floozit1
is 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 retain
and 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 retain
and release
. Don’t do it. This also goes for autorelease
. And, since you never override release
, you also never need to override retainCount
.
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
sharedFramistan
method. It tests whether the One True Instance exists; if not, it creates the object and remembers it in file-scope static
storage. Then it returns the One True Instance.
- We’ll allow going through
alloc
and init
, and return the same instance. We’ll do this in allocWithZone:
, as Apple did. We’ll also need to make sure init
doesn’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:
sharedFramistan
always returns the same object.
alloc
/init
always produce the same object (which, itself, is the same object as sharedFramistan
).
alloc
/init
will not return an object confused by multiple init
messages.
- Over-releasing causes a crash.
- Keeping
alloc
/allocWithZone:
/retain
balanced with release
never causes a crash.
- If
[super init]
returns a different object, alloc
/init
won’t break.
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.
Take-aways
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 retain
, release
, retainCount
, or autorelease
. Ever.
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 +initialize
to -init
, as suggested by Uli Kusterer.
UPDATE 2010-09-04: Replaced inline code sample with links to the Mercurial repository for the sample.