I have a class MyClass. It has instance variables passedInVar1, passedInVar2, etc. whose values will be passed in from the object that requests the initialization. It also has instance variables decodedVar1, decodedVar2, etc. that will be decoded from an archive -- or set to a default value if there is no archive.
According to Apple,
When an object receives an initWithCoder: message, the object should first send a message to its superclass (if appropriate) to initialize inherited instance variables, and then it should decode and initialize its own instance variables.
But Apple also says that a class should have a single designated initializer.
What is the best way to deal with all of this?
Apples says that:
designated initializer The init... method that has primary responsibility for initializing new instances of a class. Each class defines or inherits its own designated initializer. Through messages to self, other init... methods in the same class directly or indirectly invoke the designated initializer, and the designated initializer, through a message to super, invokes the designated initializer of its superclass. [emp added]
In principle, the designated initializer is the one init method that all other init methods call. It is not, however, the only init method. Neither does each class have to have its own. More often in practice the designated initializer is actually the super class' init.
The major function of initWithCoder
is to allow for initialization from an archived object. In the case of a class which requires some specific data, it's designated initializer will accept that data. initWithCoder
then simply unpacks the archive and then calls the designated initializer.
For example, the designated initializer for UIView is initWithFrame:
. So, UIView's initWithCoder
looks something like:
- (id)initWithCoder:(NSCoder *)decoder{
CGRect theFrame= //...uppack frame data
self=[self initWithFrame:theFrame];
return self;
}
The point of the designated initializer is to create a central point that all initialization has to pass through in order to ensure that each instances is completely initialized regardless of where the data came from or the circumstances of the initialization.
That should never be taken to mean that a class can only have one initializer method.
From comments:
In particular, how do I pass values for some of my ivars in when initialization is happening via initWithCoder?
Well, you don't. The entire point of initWithCoder is that you are dealing with a freeze dried instance of your class that contains all the data necessary to recreate the object.
The NSCoding protocol makes your class behave like the brine shrimp they sell as "Sea Monkeys" in the comic books. The coding methods dehydrates/freeze dries the brine-shrimp/instances. The decoding methods hydrates the brine-shrimp/instances just like pouring the brine shrimp into water does. Just like the the brine-shrimp have everything they need to start living except water, a coded object saved on disk has all the data needed to recreate itself once initialized with the coder.
The canonical example of this is a nib file. A nib file is just a bunch of freeze dried instances of UI elements and controllers. A UIViewController and its UIViews in a nib have all the data they need to initialize themselves coded into the xml of the nib file. When you call initFromNib
directly or with an IBOutlet, it calls each class' intiWithCoder:
method.
If you don't save the complete object when you freeze dry it, then the attributes that don't get freeze dried aren't needed for the instance object to exist.
You just set those ancillary attributes after the object has been initialized.
To inline the designated initializer, you just decode first and then call the designated initializer. Like so:
-(id) initWithRequiredValue:(id) someValue otherRequiredValue:(id) anotherValue{
if (self=[super init]){
self.requiredProperty=someValue;
self.anotherRequiredProperty=anotherValue
}
return self;
}
If the super class does not support NSCoder then you start it yourself in the subclass:
- (id)initWithCoder:(NSCoder *)decoder {
id someDecodedValue=[decoder decodeObjectForKey:@"someValueKey"];
id someOtherDecodedValue=[decoder decodeObjectForKey:@"someOtherValueKey"];
self=[self initWithRequiredValue:someDecodedValue otherRequiredValue:someOtherDecodedValue];
return self;
}
That's the simplest case. If super itself supports NSCoding, then you usually just end up writing a parallel designated initializer like so:
- (id)initWithCoder:(NSCoder *)decoder {
if (self=[super initWithCoder:decoder]){
id someDecodedValue=[decoder decodeObjectForKey:@"someValueKey"];
id someOtherDecodedValue=[decoder decodeObjectForKey:@"someOtherValueKey"];
self.requiredProperty=someDecodedValue;
self.anotherRequiredProperty=someOtherDecodedValue;
}
return self;
}
I think in most cases, initWithCoder
ends up being a parallel designated initializer because it takes care of all initialization just like the designated initializer should. It doesn't look like the designated initializer because all its data is provided by the coder but it performs the same function.
This is one of those cases where theory and practice don't line up well. The "designated initializer" concept really only applies to cases wherein you create instances from scratch.