Objective-C Programming_ The Big Nerd Ranch Guide - Aaron Hillegass [71]
The solution is simple. In Appliance.m, add an init method to call initWithProductName: with a default value for the name.
- (id)init
{
return [self initWithProductName:@"Unknown"];
}
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:
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %d volts>", productName, voltage];
}
Now, in main.m, exercise the class a bit:
#import #import "Appliance.h" int main (int argc, const char * argv[]) { @autoreleasepool { Appliance *a = [[Appliance alloc] init]; NSLog(@"a is %@", a); [a setProductName:@"Washing Machine"]; [a setVoltage:240]; NSLog(@"a is %@", a); } return 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 In OwnedAppliance.h, add a mutable set of owner names and three methods. #import "Appliance.h" @interface OwnedAppliance : Appliance { NSMutableSet *ownerNames; } - (id)initWithProductName:(NSString *)pn firstOwnerName:(NSString *)n; - (void)addOwnerNamesObject:(NSString *)n; - (void)removeOwnerNamesObject:(NSString *)n; @end Notice that one of the methods you’ve declared is an initializer that takes two arguments. Implement the methods in OwnedAppliance.m: #import "OwnedAppliance.h" @implementation OwnedAppliance - (id)initWithProductName:(NSString *)pn firstOwnerName:(NSString *)n { // Call the superclass's initializer self = [super initWithProductName:pn]; if (self) { // Make a set to hold owner names ownerNames = [[NSMutableSet alloc] init]; // Is the first owner name non-nil? if (n) { [ownerNames addObject:n]; } } // Return a pointer to the new object return self; } - (void)addOwnerNamesObject:(NSString *)n { [ownerNames addObject:n]; } - (void)removeOwnerNamesObject:(NSString *)n { [ownerNames removeObject:n]; } @end 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: OwnedAppliance *a = [[OwnedAppliance alloc] initWithProductName:@"Toaster"]; 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. - (id)initWithProductName:(NSString *)pn { return [self initWithProductName:pn firstOwnerName:nil]; } Quiz time: Do you also need to implement init in OwnedAppliance? No. At this point, the following code will work fine: OwnedAppliance *a = [[OwnedAppliance alloc] init]; 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"].