init methods that take arguments

Sometimes an object can’t be initialized properly without some information from the method that is calling it. For example, imagine that an appliance can’t function without a name. (nil doesn’t count.) In this case, you need to be able to pass the initializer a name to use.

You can’t do this with init because, for now and always, init has no arguments. So you have to create a new initializer instead. Then, when another method creates an instance of Appliance, it would look like this:

A​p​p​l​i​a​n​c​e​ ​*​a​ ​=​ ​[​[​A​p​p​l​i​a​n​c​e​ ​a​l​l​o​c​]​ ​i​n​i​t​W​i​t​h​P​r​o​d​u​c​t​N​a​m​e​:​@​"​T​o​a​s​t​e​r​"​]​;​

The new initializer for Appliance is initWithProductName:, and it accepts an NSString as an argument. Declare this new method in Appliance.h:

#​i​m​p​o​r​t​ ​<​F​o​u​n​d​a​t​i​o​n​/​F​o​u​n​d​a​t​i​o​n​.​h​>​

@​i​n​t​e​r​f​a​c​e​ ​A​p​p​l​i​a​n​c​e​ ​:​ ​N​S​O​b​j​e​c​t​ ​{​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​p​r​o​d​u​c​t​N​a​m​e​;​
 ​ ​ ​ ​i​n​t​ ​v​o​l​t​a​g​e​;​
}​
@​p​r​o​p​e​r​t​y​ ​(​c​o​p​y​)​ ​N​S​S​t​r​i​n​g​ ​*​p​r​o​d​u​c​t​N​a​m​e​;​
@​p​r​o​p​e​r​t​y​ ​i​n​t​ ​v​o​l​t​a​g​e​;​
-​ ​(​i​d​)​i​n​i​t​W​i​t​h​P​r​o​d​u​c​t​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​p​n​;​

@​e​n​d​

In Appliance.m, find the implementation of init. Change the name of the method to initWithProductName: and set productName using the passed-in value.

-​ ​(​i​d​)​i​n​i​t​W​i​t​h​P​r​o​d​u​c​t​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​p​n​
{​
 ​ ​ ​ ​/​/​ ​C​a​l​l​ ​N​S​O​b​j​e​c​t​'​s​ ​i​n​i​t​ ​m​e​t​h​o​d​
 ​ ​ ​ ​s​e​l​f​ ​=​ ​[​s​u​p​e​r​ ​i​n​i​t​]​;​

 ​ ​ ​ ​/​/​ ​D​i​d​ ​i​t​ ​r​e​t​u​r​n​ ​s​o​m​e​t​h​i​n​g​ ​n​o​n​-​n​i​l​?​
 ​ ​ ​ ​i​f​ ​(​s​e​l​f​)​ ​{​

 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​S​e​t​ ​t​h​e​ ​p​r​o​d​u​c​t​ ​n​a​m​e​
 ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​P​r​o​d​u​c​t​N​a​m​e​:​p​n​]​;​

 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​G​i​v​e​ ​v​o​l​t​a​g​e​ ​a​ ​s​t​a​r​t​i​n​g​ ​v​a​l​u​e​
 ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​V​o​l​t​a​g​e​:​1​2​0​]​;​
 ​ ​ ​ ​}​
 ​ ​ ​ ​r​e​t​u​r​n​ ​s​e​l​f​;​
}​

Before you continue, build the project to make sure the syntax is right.

Now you can create an instance of Appliance with a given name. However, if you give Appliance.h and Appliance.m to another programmer, she may not realize she needs to call initWithProductName:. What if she creates an instance of Appliance in the most common way?

A​p​p​l​i​a​n​c​e​ ​*​a​ ​=​ ​[​[​A​p​p​l​i​a​n​c​e​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​

This is not an unreasonable action. As a subclass of NSObject, an instance Appliance is expected to do anything an instance of NSObject can do. And instances of NSObject respond to init messages. However, it causes a problem here because the above line of code creates an instance of Appliance that has nil for a product name and zero for voltage. And we decided earlier that every instance of Appliance needs a voltage of 120 and an actual name to function correctly. How can you prevent this from happening?

The solution is simple. In Appliance.m, add an init method to call initWithProductName: with a default value for the name.

-​ ​(​i​d​)​i​n​i​t​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​[​s​e​l​f​ ​i​n​i​t​W​i​t​h​P​r​o​d​u​c​t​N​a​m​e​:​@​"​U​n​k​n​o​w​n​"​]​;​
}​

Notice that this new overridden init doesn’t do much work – it just calls the initWithProductName: method, which does the heavy lifting.

To test out your two initializers, you’ll need a description method. Implement description in Appliance.m:

-​ ​(​N​S​S​t​r​i​n​g​ ​*​)​d​e​s​c​r​i​p​t​i​o​n​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​[​N​S​S​t​r​i​n​g​ ​s​t​r​i​n​g​W​i​t​h​F​o​r​m​a​t​:​@​"​<​%​@​:​ ​%​d​ ​v​o​l​t​s​>​"​,​ ​p​r​o​d​u​c​t​N​a​m​e​,​ ​v​o​l​t​a​g​e​]​;​
}​

Now, in main.m, exercise the class a bit:

#​i​m​p​o​r​t​ ​<​F​o​u​n​d​a​t​i​o​n​/​F​o​u​n​d​a​t​i​o​n​.​h​>​
#​i​m​p​o​r​t​ ​"​A​p​p​l​i​a​n​c​e​.​h​"​

i​n​t​ ​m​a​i​n​ ​(​i​n​t​ ​a​r​g​c​,​ ​c​o​n​s​t​ ​c​h​a​r​ ​*​ ​a​r​g​v​[​]​)​
{​

 ​ ​ ​ ​@​a​u​t​o​r​e​l​e​a​s​e​p​o​o​l​ ​{​

 ​ ​ ​ ​ ​ ​ ​ ​A​p​p​l​i​a​n​c​e​ ​*​a​ ​=​ ​[​[​A​p​p​l​i​a​n​c​e​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​a​ ​i​s​ ​%​@​"​,​ ​a​)​;​
 ​ ​ ​ ​ ​ ​ ​ ​[​a​ ​s​e​t​P​r​o​d​u​c​t​N​a​m​e​:​@​"​W​a​s​h​i​n​g​ ​M​a​c​h​i​n​e​"​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​[​a​ ​s​e​t​V​o​l​t​a​g​e​:​2​4​0​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​a​ ​i​s​ ​%​@​"​,​ ​a​)​;​

 ​ ​ ​ ​}​
 ​ ​ ​ ​r​e​t​u​r​n​ ​0​;​
}​

Build and run the program.

Let’s take our exploration of initializers further. Create a new file: a subclass of Appliance named OwnedAppliance.

Figure 29.2  Creating a subclass of Appliance

Creating a subclass of Appliance

In OwnedAppliance.h, add a mutable set of owner names and three methods.

#​i​m​p​o​r​t​ ​"​A​p​p​l​i​a​n​c​e​.​h​"​

@​i​n​t​e​r​f​a​c​e​ ​O​w​n​e​d​A​p​p​l​i​a​n​c​e​ ​:​ ​A​p​p​l​i​a​n​c​e​ ​{​
 ​ ​ ​ ​N​S​M​u​t​a​b​l​e​S​e​t​ ​*​o​w​n​e​r​N​a​m​e​s​;​
}​
-​ ​(​i​d​)​i​n​i​t​W​i​t​h​P​r​o​d​u​c​t​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​p​n​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​i​r​s​t​O​w​n​e​r​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​n​;​
-​ ​(​v​o​i​d​)​a​d​d​O​w​n​e​r​N​a​m​e​s​O​b​j​e​c​t​:​(​N​S​S​t​r​i​n​g​ ​*​)​n​;​
-​ ​(​v​o​i​d​)​r​e​m​o​v​e​O​w​n​e​r​N​a​m​e​s​O​b​j​e​c​t​:​(​N​S​S​t​r​i​n​g​ ​*​)​n​;​

@​e​n​d​

Notice that one of the methods you’ve declared is an initializer that takes two arguments.

Implement the methods in OwnedAppliance.m:

#​i​m​p​o​r​t​ ​"​O​w​n​e​d​A​p​p​l​i​a​n​c​e​.​h​"​

@​i​m​p​l​e​m​e​n​t​a​t​i​o​n​ ​O​w​n​e​d​A​p​p​l​i​a​n​c​e​

-​ ​(​i​d​)​i​n​i​t​W​i​t​h​P​r​o​d​u​c​t​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​p​n​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​i​r​s​t​O​w​n​e​r​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​n​
{​
 ​ ​ ​ ​/​/​ ​C​a​l​l​ ​t​h​e​ ​s​u​p​e​r​c​l​a​s​s​'​s​ ​i​n​i​t​i​a​l​i​z​e​r​
 ​ ​ ​ ​s​e​l​f​ ​=​ ​[​s​u​p​e​r​ ​i​n​i​t​W​i​t​h​P​r​o​d​u​c​t​N​a​m​e​:​p​n​]​;​

 ​ ​ ​ ​i​f​ ​(​s​e​l​f​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​M​a​k​e​ ​a​ ​s​e​t​ ​t​o​ ​h​o​l​d​ ​o​w​n​e​r​ ​n​a​m​e​s​
 ​ ​ ​ ​ ​ ​ ​ ​o​w​n​e​r​N​a​m​e​s​ ​=​ ​[​[​N​S​M​u​t​a​b​l​e​S​e​t​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​

 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​I​s​ ​t​h​e​ ​f​i​r​s​t​ ​o​w​n​e​r​ ​n​a​m​e​ ​n​o​n​-​n​i​l​?​
 ​ ​ ​ ​ ​ ​ ​ ​i​f​ ​(​n​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​o​w​n​e​r​N​a​m​e​s​ ​a​d​d​O​b​j​e​c​t​:​n​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​}​
 ​ ​ ​ ​}​
 ​ ​ ​ ​/​/​ ​R​e​t​u​r​n​ ​a​ ​p​o​i​n​t​e​r​ ​t​o​ ​t​h​e​ ​n​e​w​ ​o​b​j​e​c​t​
 ​ ​ ​ ​r​e​t​u​r​n​ ​s​e​l​f​;​
}​

-​ ​(​v​o​i​d​)​a​d​d​O​w​n​e​r​N​a​m​e​s​O​b​j​e​c​t​:​(​N​S​S​t​r​i​n​g​ ​*​)​n​
{​
 ​ ​ ​ ​[​o​w​n​e​r​N​a​m​e​s​ ​a​d​d​O​b​j​e​c​t​:​n​]​;​
}​

-​ ​(​v​o​i​d​)​r​e​m​o​v​e​O​w​n​e​r​N​a​m​e​s​O​b​j​e​c​t​:​(​N​S​S​t​r​i​n​g​ ​*​)​n​
{​
 ​ ​ ​ ​[​o​w​n​e​r​N​a​m​e​s​ ​r​e​m​o​v​e​O​b​j​e​c​t​:​n​]​;​
}​

@​e​n​d​

Note that this class doesn’t initialize voltage or productName. The initWithProductName: in Appliance takes care of those. When you create a subclass, you typically only need to initialize the instance variables that you introduced; let the superclass take care of the instance variables that it introduced.

Now, however, you face the same situation as you did with Appliance and its superclass’s initializer, init. At the moment, one of your co-workers might create a terrible bug with this line of code:

O​w​n​e​d​A​p​p​l​i​a​n​c​e​ ​*​a​ ​=​ ​[​[​O​w​n​e​d​A​p​p​l​i​a​n​c​e​ ​a​l​l​o​c​]​ ​i​n​i​t​W​i​t​h​P​r​o​d​u​c​t​N​a​m​e​:​@​"​T​o​a​s​t​e​r​"​]​;​

This code will cause the initWithProductName: method in Appliance to run. This method knows nothing about the ownerNames set, which means ownerNames will not get properly initialized for this OwnedAppliance instance.

The fix here is the same as before. In OwnedAppliance.m, add an implementation of the superclass’s initializer initWithProductName: that calls initWithProductName:firstOwnerName: and passes a default value for firstOwnerName.

-​ ​(​i​d​)​i​n​i​t​W​i​t​h​P​r​o​d​u​c​t​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​p​n​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​[​s​e​l​f​ ​i​n​i​t​W​i​t​h​P​r​o​d​u​c​t​N​a​m​e​:​p​n​ ​f​i​r​s​t​O​w​n​e​r​N​a​m​e​:​n​i​l​]​;​
}​

Quiz time: Do you also need to implement init in OwnedAppliance? No. At this point, the following code will work fine:

O​w​n​e​d​A​p​p​l​i​a​n​c​e​ ​*​a​ ​=​ ​[​[​O​w​n​e​d​A​p​p​l​i​a​n​c​e​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​

Why? Because there is no implementation of init in OwnedAppliance, this line will trigger the init method implemented in Appliance, which calls [self initWithProductName:@"Unknown"]. self is an instance of OwnedAppliance, so it calls initWithProductName: in OwnedAppliance, which calls [self initWithProductName:pn firstOwnerName:nil].

What you wind up with is a chain of initializers that call other initializers.

Figure 29.3  Initializer chain

Initializer chain

Notice that Figure 29.3 shows one shaded initializer for each class. This initializer is the designated initializer for that class. init is the designated initializer for NSObject, initWithProductName: is the designated initializer for Appliance, and initWithProductName:firstOwnerName: is the designated initializer for OwnedAppliance. A class has only one designated initializer method. If the class has other initializers, then the implementation of those initializers must call (directly or indirectly) the designated initializer. Thus, the designated initializer acts as a funnel-point.

When you create a class whose designated initializer has a different name than its superclass’s designated initializer (as you did in Appliance and OwnedAppliance), you have a responsibility to document that in the header file. Add the appropriate comment in Appliance.h:

#​i​m​p​o​r​t​ ​<​F​o​u​n​d​a​t​i​o​n​/​F​o​u​n​d​a​t​i​o​n​.​h​>​

@​i​n​t​e​r​f​a​c​e​ ​A​p​p​l​i​a​n​c​e​ ​:​ ​N​S​O​b​j​e​c​t​ ​{​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​p​r​o​d​u​c​t​N​a​m​e​;​
 ​ ​ ​ ​i​n​t​ ​v​o​l​t​a​g​e​;​
}​
@​p​r​o​p​e​r​t​y​ ​(​c​o​p​y​)​ ​N​S​S​t​r​i​n​g​ ​*​p​r​o​d​u​c​t​N​a​m​e​;​
@​p​r​o​p​e​r​t​y​ ​i​n​t​ ​v​o​l​t​a​g​e​;​

/​/​ ​T​h​e​ ​d​e​s​i​g​n​a​t​e​d​ ​i​n​i​t​i​a​l​i​z​e​r​
-​ ​(​i​d​)​i​n​i​t​W​i​t​h​P​r​o​d​u​c​t​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​p​n​;​

@​e​n​d​

and in OwnedAppliance.h:

#​i​m​p​o​r​t​ ​"​A​p​p​l​i​a​n​c​e​.​h​"​

@​i​n​t​e​r​f​a​c​e​ ​O​w​n​e​d​A​p​p​l​i​a​n​c​e​ ​:​ ​A​p​p​l​i​a​n​c​e​ ​{​
 ​ ​ ​ ​N​S​M​u​t​a​b​l​e​S​e​t​ ​*​o​w​n​e​r​N​a​m​e​s​;​
}​
/​/​ ​T​h​e​ ​d​e​s​i​g​n​a​t​e​d​ ​i​n​i​t​i​a​l​i​z​e​r​
-​ ​(​i​d​)​i​n​i​t​W​i​t​h​P​r​o​d​u​c​t​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​p​n​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​i​r​s​t​O​w​n​e​r​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​n​;​
-​ ​(​v​o​i​d​)​a​d​d​O​w​n​e​r​N​a​m​e​s​O​b​j​e​c​t​:​(​N​S​S​t​r​i​n​g​ ​*​)​n​;​
-​ ​(​v​o​i​d​)​r​e​m​o​v​e​O​w​n​e​r​N​a​m​e​s​O​b​j​e​c​t​:​(​N​S​S​t​r​i​n​g​ ​*​)​n​;​

@​e​n​d​

Thus, we arrive at the rules that all stylish Objective-C programmers follow when writing initializers:

  • If a class has several initializers, only one should do the real work. That method is known as the designated initializer. All other initializers should call, either directly or indirectly, the designated initializer.

  • The designated initializer will call the superclass’s designated initializer before initializing its instance variables.

  • If the designated initializer of your class has a different name than the designated initializer of its superclass, you must override the superclass’s designated initializer so that it calls the new designated initializer.

  • If you have several initializers, clearly document which is the designated initializer in the header.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset