Twitter Engineering Blog posted a very good article about Objective-C initializers. However, I feel the rules it compiled are not clear enough. So I'm trying to give my own rules here:
- Designated initializers call
- Secondary initializers call
[self initializer], including
[self another_seconder_initializer], but finally one of them must call
- Subclass can replace superclass's designated initializers with new designated initializers, making these replaced designated initializers secondary in subclass. By rule 2, these initializers must be overridden in subclass to call
- A class may have multiple designated initializers each of which is independent of any other. They are usually used to initialize objects from different sources. Each designated initializer can have its own secondary initializers.
initWithCoder:is a designated initializer because it initialize objects from a different source —
NSCoder— than any other designated initializer — see rule 4. By rule 1, it should call
For rule 5 and the contradiction to Twitter's article, I have more to say.
There’s a problem with the example provided in the documentation for initWithCoder:, specifically the call to [super (designated initializer)]. If you’re a direct subclass of NSObject, calling [super (designated initializer)] won’t call your [self (designated initializer)]. If you’re not a direct subclass of NSObject, and one of your ancestors implements a new designated initializer, calling [super (designated initializer)] WILL call your [self (designated initializer)]. This means that apple’s suggestion to call super in initWithCoder encourages non-deterministic initialization behavior, and is not consistent with the solid foundations laid by the designated initializer pattern. Therefore, my recommendation is that you should treat initWithCoder: as a secondary initializer, and call [self (designated initializer)], not [super (designated initializer)], if your superclass does not conform to NSCoding.
The first highlighted part is logically incorrect. Calling
[super designated_initializer] will never call
[self designated_initializer]. I guess the author was thinking of calling
[super old_designated_initializer] where
super denotes an ancestor who overrides
old_designated_initializer to call its
new_designated_initializer. But as stated in rule 3, after it is replaced by a new designated initializer the old designated initializer is no longer designated initializer. Calling
[super old_designated_initializer] will call
[self new_designated_initializer](overridden or inherited) but then you are calling a secondary initializer from a designated initializer — remember
initWithCoder: is a designated initializer — and you are doing it wrong by rule 1.
But I do feel a little uncomfortable with the current situation of
initWithCoder: being designated initializer. If you have ever written any
UIViewController subclasses that may both be unarchived from a nib file and created directly in code, you should know my feeling. For such a
UIViewController subclass, we are forced to override both
initWithNibName:bundle: and put the same initialization code in both. Encapsulating the initialization code in a method alleviates it.
Should Apple have made
initWithCoder: secondary initializer in their root
NSCoding conformable classes, we won't need to override
initWithCoder: in subclasses of these classes if we don't do additional deserialization. We just override the designated initializer and it will be called by
It is exactly the author's recommendation. So except the fact it is against Apple's rules and practice, is it advisable in real projects? I don't think so.
We subclass Apple's root
NSCoding conforming classes most of the time so we must accept Apple's way. We should not use a contrary pattern for our own root
NSCoding conforming classes. Otherwise, we'll have an inconsistent system.
Subclasses with additional properties may need to override
initWithCoder: to decode these properties from the coder. But overriding a secondary initializer violates rule 2. By contradiction,
initWithCoder: can not be secondary initializer.
Finally, if you agreed that
initWithCoder: does initialize objects from a special source you should also agree that it is indeed a designated initializer.