Reputation: 29
I'm having trouble understanding the following in Stephen Kochan's book "Programming in Objective-C" - 4th Edition. I hope that you can help me understand it.
In Chapter 10 on the section on "Initialising Objects", Stephen writes:
You should adhere to the following two strategies when writing initialisers.
It might be the case that you want to do something special whenever one of the objects in your class gets initialised. For example, that's the perfect place to create the objects that your class uses and references through one or more instance variables. A perfect example of that would be our Rectangle class; it would be reasonable to allocate the rectangle's XYPoint origin in the init method. To do so, we just have to override the inherited init method.
There's a standard 'template' that's used for overriding init, and it looks like this:
- (id) init
{
self = [super init];
if (self) {
// initialisation code here.
}
return self;
}
This method invokes the parent initialiser first. Executing the parent's initialiser ensures that any inherited instance variables are properly initialised.
You must assign the result of executing the parent's init method back to self because an initialiser has the right to change the location of the object in memory (meaning its reference will change).
If the parent's initialiser succeeds, the value returned will be non-nil, as tested by the if statement. As the comment indicates, inside the block that follows is where you can put your own custom code for your object. This will often involve allocating and initialising instance variables that are in your class.
OK, so thus far I have understood what Stephen Kochan is trying to say but I'm completely mystified by the next part. I hope you can help.
If you class contains more than one initialiser, one of them should be your designated initialiser and all the other initialisation methods should use it. Typically, that is your most complex initialisation method (usually, one that takes the most arguments).
So, my first question is: why have all the other initialisation methods if they're all going to use one particular one in this case the "designated" initialiser?
Stephen Kochan goes on to say:
Creating a designated initialiser centralises your main initialisation code in a single method. Anyone subclassing your class can then override your designated initialiser to ensure that new instances are properly initialised.
Could you give an example of this? I'm not quite sure I understood what he's saying.
Stephen continues:
Based on that discussion, your initialisation method initWith:over: for your Fraction class might look like this:
- (Fraction *) initWith:(int)n over:(int)d
{
self = [super init];
if (self) {
[self setTo: n over: d];
}
return self;
}
Following the initialisation of super (and its success, as indicated by the return of a nonzero value) you use the
setTo:over:
method to set the numerator and denominator of your Fraction. As with other initialisation methods, you are expected to return the initialised object, which you do here.Program 10.1 tests your new
initWith:over:
initialisation method.
#import "Fraction.h"
int main (int argc, char *argv[])
{
@autoreleasepool {
Fraction *a, *b;
a = [[Fraction alloc] initWith: 1 over: 3];
b = [[Fraction alloc] initWith: 3 over: 7];
[a print];
[b print];
}
return 0;
}
Output:
1/3
3/7
So far I have understood the code. The following part I don't understand at all:
To adhere to the rule stated earlier about a designated initialiser, you should also modify init in your Fraction class. That's particularly important if your class might be subclassed.
Here's what the init method could look like:
- (id)init
{
return [self initWith:0 over:0];
}
Why is this important if we want to subclass?
Stephen Kochan continues:
When your program begins execution, it sends the initialisation call method to all your classes. If you have a class and associated subclasses, the parent class gets the message first. This message is sent only once to each class, and it is guaranteed to be sent before any other messages are sent to the class. The purpose is for you to perform any class initialisation at that point. For example, you might want to initialise some static variables associated with that class at that time.
I didn't really understand this last part either. I hope that you can help.
Upvotes: 2
Views: 268
Reputation: 100751
the "most complex initializer" is the one with most parameters. you should make all other init-methods to use that one:
There could be a initWithValueA:andB:andC:
along with initWithValueC:
, initWithValueA:
, initWithValueB:andC:
and so on. all those "less complex" methods should invoke the "most complex" method using default values for all other parameters (likely 0, nil
, ...)
([super init]
will call init-method of the superclass so you can overwrite init
safely to also call the "most complex" init-Method and set up your default state or throw an exception - if you want to call the custom init
you'd have to use [self init]
).
The last part you mentioned covers the static initializer ("static class constructor"). its declaration starts with a '+' sign which means it is a class method ("static method") like alloc. in C# this is static MyClass() {...}
, in Java just static { ... }
Upvotes: 0
Reputation: 366053
This is a whole slew of questions in one, which makes it very hard to answer.
So, my first question is: why have all the other initialisation methods if they're all going to use one particular one in this case the "designated" initialiser?
Mainly for the convenience of your callers. For example, let's say your designated initializer is initWithX:y:width:height:
, but you find yourself writing things like this all over the place:
[[MyRect alloc] initWithX:0 y:0 width:0 height:0]
[[MyRect alloc] initWithX:myPoint.x y:myPoint.y width:mySize.width height:mySize.height]
You might want to add another couple of initializers, so you can just do this:
[[MyRect alloc] initWithEmptyRect]
[[MyRect alloc] initWithPoint:myPoint size:mySize]
Of course it's never actually necessary, but it's worth doing for the same reason it's always worth wrapping something up in a function rather than repeating yourself over and over.
You can find more realistic examples by looking through various classes that come with Foundation/Cocoa — many of them have lots of different initializers, and often they wrap up a lot more work than just calling .x, .y, .width, and .height. For example, NSDictionary has methods like -initWithContentsOfURL:
. In theory, you could always read the contents of that URL, parse the plist into a pair of C-style arrays of objects and keys, then call -initWithObjects:forKeys:count:
, so this isn't actually necessary. But which would you rather do?
"Creating a designated initialiser centralises your main initialisation code in a single method. Anyone subclassing your class can then override your designated initialiser to ensure that new instances are properly initialised".
Could you give an example of this? I'm not quite sure I understood what he's saying.
Let's say someone creates this class:
@interface MySuperRect: MyRect
- (id)initWithX:x y:y width:width height:height;
@end
He doesn't need to override all of your init methods, just this one. Let's say you do this:
[[MySuperRect alloc] initWithEmptyRect]
Because MySuperRect hasn't implemented initWithEmptyRect, this will use the implementation from MyRect, which just calls the designated initializer. But MySuperRect has implemented the designated initializer. So, the override will get called.
I think that also answers your third question. (I think it's the third question.)
For your fourth and final question, you'll need to explain what part of it you don't understand before anyone can help you. Do you, for example, get that classes are just a (slightly) special kind of object, and generally get how object initialization works, and get how class initialization is special, but not understand why you'd want to initialize static variables there?
Upvotes: 3
Reputation: 3113
Why have all the other initialisation methods if they're all going to use one particular one in this case the "designated" initialiser?
You'd do this for convenience. Your "designated" initializer (as Stephen Kochan mentions) is usually the one with the most arguments. All other initializers are convenience methods that call that one with some default settings. For example, if I have a "Car" class where I can specify the numbers of various elements:
@implementation Car
//Designated initializer - has tons of arguments
- (id)initWithDoors:(int)doors windows:(int)windows wheels:(int)wheels axles:(int)axles
{
//implementation
}
//most axles have two wheels and most doors have a window (and there's the front and back window)
- (id)initWithDoors:(int)doors wheels:(int)
{
return [self initWithDoors:doors windows:doors+2 wheels:wheels axles:wheels/2];
}
//most cars have four doors, four wheels, six windows, and two axles
- (id)init
{
return [self initWithDoors:4 windows:6 wheels:4 axles:2];
}
@end
A subclasser, then, just calls the designated initializer. Here's an example for a Coupe class. (A coupe has two doors.)
@implementation Coupe //extends Car
//our coupe can have a sunroof, which adds a window
- (id)initWithSunroof:(BOOL)sunroof
{
self = [super initWithDoors:2 windows:(sunroof?4:5) wheels:4 axles:2];
if (self) {
//initialization
}
return self;
}
//a default coupe has no sunroof
- (id)init
{
return [self initWithSunroof:NO];
}
@end
Note that the -initWithSunroof:
method is the designated initializer for the subclass.
Why is this important if we want to subclass?
Finally, you want to implement -init
so a naïve subclasser can call that and get all the defaults without having to investigate your class too much if they don't need to.
I didn't really understand this last part either.
The class initializer is formatted as such:
+ (void)initialize
{
//implementation
}
It sets up any class-level variables you may have. These affect all instances of a class. (The difference between class and instance variables is well-explained elsewhere and is beyond the scope of this answer. Ask another question if you remain confused.)
Upvotes: 1