iPhone — is initWithCoder an exception to the usual designated initializer design pattern?

一世执手 提交于 2019-11-29 22:30:33

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.

Edit01

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!