Note: What follows is another post in an on-going series for developers who are interested in learning to write applications for the iPhone. The entire series can be found here: iPhone Developer .
The code for creating new instances of a class generally looks as follows:
SomeClass *ptr = [[SomeClass alloc] init];
In this case, we send a message (alloc) to the recevier (SomeClass) to create a new instance; the object returned from this message, then sends a message (init) to initialize instance variables within the class.
I briefly talked about the importance of the order in which objects are allocated and initialized. You can read more about that here: Memory Management in Objective-C . In this post, I want to introduce how to override the init method of the NSObject class, more specifically, how to work with multiple initializers.
In the init method of the NSObject class, no initialization takes place, it simply returns self .
The basic format of a method to override the initializer looks as follows:
-idinit if self = super init // Initialization code here return self;
Its important to call the super init method first to ensure that instance variables up the inheritance hierarchy are initialized in the proper order (from top to bottom). Also, if your initialize code fails, you need to return nil from the overriden init method. Finally, return a reference to self as shown above.
So lets assume we have a block of code as follows:
SomeClass *ptr = SomeClass alloc init; ptr setStr:@"Testing";
Here we initialize a new instanc of SomeClass, and follow this with a call to set an instance variable to the specified string (@Testing). One common means to accomplish this is to create a new initializer in which we pass in the parameter (the string in this case) as part of the original creation of the object. For example:
SomeClass *ptr = SomeClass alloc initWithStr: @"Testing";
We can also take this one step further. Lets say that we also wanted to initialize an instance variable that was a pointer to a date object (NSDate *). In that case, we might want an additional initilizer that looks as follows:
SomeClass *ptr = [[SomeClass alloc] initWithStrAndDate:@"Testing" date:[NSDate date]];
It quite common for classes to have more than one initializer for creating new objects, allowing variations as shown above. This also implies that the initialization methods need to work in harmony. Designated initializers are the means to achieve this harmonious state.
When working with a class that has more than one initialization method (as shown above), its important that one of the initializers drives the whole process. Put another way, only one of the initializer methods does the actual work of calling the super class initializer and initializing the instance variables of the object.
This process is actually quite simple. Lets assume we have a class implementation as follows:
@interface SomeClass : NSObject NSString *str; NSDate *date; // Designated initializer -idinitWithStrAndDate: NSString *inStr date:NSDate *inDate; -idinitWithStr: NSString *inString; -idinit;
The way this should work, is that a call to the initializer init should call initWithStr . A call to the initializer initWithStr should call initWithStrAndDate . Following this process, all the actual initialization work occurs in initWithStrAndDate . This method (the one that does the work) is known as the designated initializer .
A general rule of thumb (although not always the case) is that the designated initializer is the initializer with the most parameters.
So lets see how this might look within the implementation of a class:
@implementation SomeClass // ========================== // = Designated initializer = // ========================== -idinitWithStrAndDate: NSString *inString date:NSDate *inDate if self = super init self setStr:inString; self setDate:inDate; return self; -idinitWithStr: NSString *inString // Either of these will work return self initWithStrAndDate:inString date:NSDate date; // return [self initWithStrAndDate:inString date:nil]; -idinit return self initWithStr:nil; ... @end
Notice how starting with init , it calls the next initializer in the chain, initWithStr , passing in a default value (in this case, nil). This method then calls the designated initializer, again, passing in a default value, this time for the date. And notice how the designated initializer is the only method that calls super .
Working with multiple initializers is a simple process of ensuring that each initializer calls up through the initialization chain within the class. This ensures that all instance variables are initialized in just one place, and that the super method is called such that all instance variables of classes further up the hierarchy are initialized first (from top down).
Here is a copy of the Initializers Xcode project if would like to try the above example within Xcode.