mikeash.com: just this guy, you know?

Posted at 2008-10-09 23:43 | RSS feed (Full text feed) | Blog Index
Next article: Key-Value Observing Done Right
Previous article: It's a Poor Carpenter Who Blames His Tools or: Xcode Sucks Again
Tags: cocoa init initializer objectivec super
The How and Why of Cocoa Initializers
by Mike Ash  

One of the longest ongoing controversies in the Cocoa community is how to write your init methods. More specifically, how to properly call your superclass's initializer. In the hopes of putting this controversy to rest, I want to walk through the right way to write an initializer and exactly why this is the right way.

How it must be done
It should come as no surprise that the right way to write an initializer is the Apple Way, which should be familiar to everyone reading this:

- init {
    if((self = [super init])) {
        // set up instance variables and whatever else here
    }
    return self;
}

Minor variations are of course just fine, as long as they're equivalent. For example, some people like to check self for nil and then return nil immediately to avoid putting initialization in a separate block. Some people like to break out the assignment, then check just plain self in the if statement. These all do the same thing, so it's just a matter of taste.

Call super or self?
For the sake of completeness, I'll first cover a more obvious part of this method, the call to [super init]. If you're really new to Objective-C and came from a language like C++ or Java, this may puzzle you. It's here because Objective-C initializers are just plain methods like any other method. If you want the superclass initializer to be called, then you have to call it yourself. You also have a choice here, between calling super and calling self. Which one you use depends on circumstances. If you're not sure, you can apply one of these rules of thumb:

  1. Call super if and only if the method name matches.
  2. Call super if and only if you're implementing your class's designated initializer. (This rule is more accurate, but sometimes harder to apply.)

Checking nil
And again for the sake of completeness, some may wonder why the if statement is there at all. The reason is because your superclass may fail to initialize (for example, if the parameters weren't consistent) and the standard way to indicate this is to release the object and return nil from the initializer. If you continue initializing your state after this happens, you will crash. The if statement lets you fail gracefully if your superclass failed.

The assignment
Now on to the meat of the controversy. The above is fairly obvious and I doubt anyone will disagree with it. But that assignment to self is frequently disputed. Let's run through the myths one by one, from most obvious to least obvious.

Myth: The superclass initializer will only ever return nil or self.
Fact: Many Cocoa classes return a different pointer from their initializer.

Myth: Fine, but that only happens with class clusters, and only when you don't subclass them.
Fact: It's true that class clusters don't do this to their subclasses, but other classes can and do.

Myth: But those other subclasses are things you would never want to subclass anyway, like NSColorPanel, or it only happens when you do something wrong, like subclass a singleton but fail to make that subclass the singleton.
Fact: These are indeed situations where it can happen, but it's not limited to these situations.

Myth: The superclass initializer has to return self because my initializer can only deal with instances of my class.
Fact: The superclass initializer could return a different instance of your class.

Myth: It can't return a different instance because you're not allowed to re-initialize instances.
Fact: It's true about re-initialization, but it could allocate a new instance.

Myth: But it has no reason to do that. It already has a new instance right there.
Fact: It has a good reason to do that if it wants some extra storage at the end, like if it created a dynamic subclass of your class and wants to use an instance of that subclass.

This is why the standard initializer pattern is the only one that works. Cocoa classes can and do deallocate the original instance, then allocate a new instance of the same class (or a subclass) and return it from their initializers. This is admittedly rare, but it is legal and it does happen. And that, in a nutshell, is why the standard Apple initializer pattern is the only correct way.

Conclusion
To summarize, the superclass's initializer can return one of three things, and the standard Apple pattern deals with them all:

  1. self (This is what you get the vast, vast majority of the time.)
  2. nil (On failure.)
  3. A new instance of your class (Rare but legitimate.)

Many people like to leave off the assignment and just check for nil. This works fine for cases 1 and 2 but will fail in very confusing ways for case 3.

There are actually a couple of other things that can potentially be returned:

  1. An instance of a different class
  2. An existing instance of your class

Case 4 only happens if you did something wrong, so it's not something you should be handling. Case 5 only happens if you either did something wrong, or if you subclassed a singleton. If you subclass a singleton then you should make sure your initializer can handle re-initializing the existing singleton instance. This is just good practice for singletons in general anyway.

(As an aside, the standard pattern is not necessary if you subclass NSObject directly. This is because -[NSObject init] in documented to do nothing and always return self. However, using the principle that we should always write code that's robust to changes, in this case to changes in what class you inherit from, I very much recommend using the standard pattern even for direct NSObject subclasses.)

So there you have it. This is how to write your initializers, and why you should write them that way. I hope it's clear enough not to generate dispute, but if you must, comment away.

References

  1. re: self = [super init] debate. - Ben Trumbull posts on cocoa-dev to explain some key points on the subject.
  2. NSManagedObject Class Reference - This class is an example of a class which returns a new, different instance of the class being initialized.

Did you enjoy this article? I'm selling whole books full of them! Volumes II and III are now out! They're available as ePub, PDF, print, and on iBooks and Kindle. Click here for more information.

Comments:

very nice summary. :-)
Oh no you didn't just lay smack down on the Shipley?
Here I thought this would be related to the recent discussion on objc-language about how to gracefully fail in init. :-)
I had read a lot of the different discussions on this topic and yours has convinced me to use the "Apple Way". I stand corrected.
Here's another reason to stick to the Apple init pattern: Performance.

The code

if ([super init]!=nil) ...


actually produces 5 more machine instructions than

if ( (self=[super init])!=nil ) ...


(Xcode 3.1, gcc 4.0.1, Intel, standard Release build optimization settings)

I have no idea if this is a special gcc optimization that recognizes and optimizes the standard init pattern or not, but reassigning self in the init method actually produces less (and presumably faster) code than trying to omit it.

How's that for win-win?
Great post. However, il would be even greater if you added evidence to each of your "fact" statements, however trivial that evidence might be. Your link to NSManagedObject is definitely evidence.

Finally, there is still another unclear point: when [super init] returns *another* instance, what happened to the original instance? Who is responsible for releasing it? I have seen people doing things such as:

newself = [super init];
if (newself != self) {
  [self release];
  self = newself
}
if (self) {
 ...
}
return self;

Which I believe is wrong, as I think that releasing self is the superclass's responsibility *if* it returns a new instance.

Am I correct?

JD
I'm pretty sure that the link to NSManagedObject is evidence for every single one of those facts. Indeed, looking through them, "NSManagedObject does it" is proof of every single one of them. What do you want beyond that?

As for your question, standard memory management rules apply. Within any given scope, the sum of alloc/copy/retain should match the sum of release/autorelease. That means your example would be wrong, because you have an unbalanced release. And the superclass -init would have an unbalanced alloc, copy, or retain. So, yes, if the superclass -init creates a new object to return then it is necessarily responsible for releasing the old one.

One case where I often return a new instance is when I want to load a configured instance from a nib inside the init method. Happens quite often.
JD, I think you are correct. Tthe superclass should send a [self autorelease] message before passing the new instance on. The code in your example might horribly fail by trying to assign newself to self after self is deallocated.
Thanks for this great post that explains many of my irritations with Obj-C initialization methods.

Though one question remains and I couldn't find an answer in Apple's documentation (though I might have overseen it):

Are all accesses to ivars implicitly channeled through self?

Otherwise assigning a new instance to self by way of self = [super init] would result in subsequent access to ivars of an instance that shouldn't exist anymore.

Cheers,
Bjoern
Yes, any time you write 'someivar' in code, it gets implicitly translated to 'self->someivar'. Reassigning to the self pointer will redirect all ivar accesses.
I was looking at the following Apple doc page.

http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/FrameworkImpl.html

In it the example code indicates that in case of an error self should be released. What is going on here?

I don't believe most of the ntializers I've seen have ever done this.
If you return nil on error, then you must release self, otherwise the object will leak.
I think I see my confusion now. In the case of getting self back from super and then having a problem initializing you are responsible for releasing the half baked instance.

I'm trying to make the transition to Cocoa full time. While I feel like I have a good understanding of retain/release cycles, I am struggling to understand proper initialization routines. This article and discuss have made this much more clear to me.

Thanks!

Comments RSS feed for this page

Add your thoughts, post a comment:

Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.

Name:
The Answer to the Ultimate Question of Life, the Universe, and Everything?
Comment:
Formatting: <i> <b> <blockquote> <code>.
NOTE: Due to an increase in spam, URLs are forbidden! Please provide search terms or fragment your URLs so they don't look like URLs.
Code syntax highlighting thanks to Pygments.
Hosted at DigitalOcean.