Objective-C Initializer Design Rules
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
[super designated_initializer]
. - Secondary initializers call
[self initializer]
, including[self another_seconder_initializer]
, but finally one of them must call[self designated_initializer]
. - 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
[self new_designated_initializer]
. - 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[super designated_initializer]
not[self designated_initializer]
.
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 UIView
or 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 initWithCoder:
and 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 initWithCoder:
.
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.