Learn Objective-C: Complex Initializers

The subject of initializer methods deserves a bit of special attention. Most importantly, in addition to the default initializer method that is inherited from NSObject, most classes also have other initializers that take certain arguments. All initializers, however, follow the same pattern. Let’s look at an example.

Convenience Initializers

Additional initializers are often known as convenience initializers because you can initialize the object and set some or all of its instance variables (and perhaps other values) in one method call. To begin, let’s take a look at some of NSString‘s initializers. These include initWithString:, which takes an NSString as an argument and creates a new string from that, initWithFormat:, which takes a formatted string and a corresponding number of arguments, and plugs in the proper values for each format specifier, and returns the completed string, and initWithContentsOfFile:encoding:error:, which creates a string out of any text file. The latter example is a perfect example of the convenience nature of these methods- doing the same with C library functions would be a lot more difficult. Note also that NSString has class methods that do the same thing (except that according to the memory management rules, the values are autoreleased).

If we were to create a Rectangle class, you might have an initializer like this:

- (id)initWithWidth:(float)w andHeight:(float)h {
    if(!(self = [super init]))
        return nil;
    self.width = w;
    self.height = h;
    return self;
}

Not much that’s new here. We’ve simply created another convenience initializer.

The Designated Initializer

The interesting bit comes when we decide to subclass our Rectangle class. Suppose we create a ColoredRectangle, or (and this is perhaps a bad example) ColoredCube. We would be adding new instance variables—at least a color ivar, and maybe a further depth measurement. Normally, the subclass would have to override all of those methods, and if the superclass added another initializer, that would mean that all subclasses would have to override that as well.

Fortunately, Cocoa has a solution- the designated initializer. Typically the one with the most arguments, all the parallel initializers call that one, and the subclasses all use the designated initializers in their calls to super. Choose an initializer to be the designated initializer, and follow through with it:

// Rectangle's initializers
- (id)initWithWidth:(float)w andHeight:(float)h {
    if(!(self = [super init]))
        return nil;
    self.width = w;
    self.height = h;
    return self;
}

- (id)initWithWidth:(float)w andArea:(float)a {
    if(!(self = [self initWithWidth:w andHeight:(a / w)]))
        return nil;
    return self;
}

// ColoredCube's initializers
- (id)initWithWidth:(float)w andHeight:(float)h {
    if(!(self = [super initWithWidth:w andHeight:h]))
        return nil;
    self.depth = 1.0;    // Default value
    self.color = [UIColor redColor];    // Ditto
    return self;
}

- (id)initWithWidth:(float)w height:(float)h depth:(float)d andColor:(UIColor *)c {
    if(!(self = [super initWithWidth:w andHeight:h]))
        return nil;
    self.depth = d;
    self.color = c;
    return self;
}

Note here that we only have to override the default initializer in the subclass, because all the other inherited initializers will then call on that overridden initializer. We can also add more initializers as necessary.

By creating a default initializer (note that there is not specific syntax to denote the designated initializer- it’s up to the programmer to determine which one is the designated and stick with it), it makes subclassing a lot easier.

This post is part of the Learn Objective-C in 24 Days course.


Previous Lesson Next Lesson