iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (13 page)

Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online

Authors: Aaron Hillegass,Joe Conway

Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming

BOOK: iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides)
6.1Mb size Format: txt, pdf, ePub
Other initializers and the initializer chain

A class can have more than one initializer. First, let’s consider a hypothetical example.
BNRItem
could have an initializer that takes only an
NSString
for the
itemName
. Its declaration would look like this:

 
- (id)initWithItemName:(NSString *)name;

In this initializer’s definition, you wouldn’t replicate the code in the designated initializer. Instead, this initializer would simply call the designated initializer, pass the information it was given as the
itemName
, and pass default values for the other arguments.

 
- (id)initWithItemName:(NSString *)name
{
    return [self initWithItemName:name
                   valueInDollars:0
                     serialNumber:@""];
}

Using initializers as a chain like this reduces the chance for error and makes maintaining code easier. For classes that have more than one initializer, the programmer who created the class chooses which initializer is designated. You only write the core of the initializer once in the designated initializer, and other initialization methods simply call that core with default values.

 

Now let’s look at a real example.
BNRItem
actually has another initializer,
init
, which it inherits it from its superclass
NSObject
. If
init
is sent to an instance of
BNRItem
, none of the stuff you put in the designated initializer will be called. Therefore, you must link
BNRItem
’s implementation of
init
to its designated initializer.

 

In
BNRItem.m
, override the
init
method to call the designated initializer with default values for all of the arguments.

 
- (id)init
{
    return [self initWithItemName:@"Item"
                   valueInDollars:0
                     serialNumber:@""];
}
 

The relationships between
BNRItem
’s initializers (real and hypothetical) are shown in
Figure 2.14
; the designated initializers are white, and the additional initializer is gray.

 

Figure 2.14  Initializer chain

 

Let’s form some simple rules for initializers from these ideas.

 
  • A class inherits all initializers from its superclass and can add as many as it wants for its own purposes.
 
  • Each class picks one initializer as its
    designated initializer
    .
 
  • The designated initializer calls the superclass’s designated initializer.
 
  • Any other initializer a class has calls the class’s designated initializer.
 
  • If a class declares a designated initializer that is different from its superclass, the superclass’s designated initializer must be overridden to call the new designated initializer.
 
Using Initializers

Currently, the code in
main.m
sends the message
init
to the new instance of
BNRItem
. With these new initializer methods, this message will run the
init
method you just implemented in
BNRItem
, which calls the designated initializer )
initWithItemName:valueInDollars:serialNumber:
) and passes default values. Let’s make sure this works as intended.

 

In
main.m
, log the
BNRItem
to the console after it is initialized but before the setter messages are sent.

 
BNRItem *p = [[BNRItem alloc] init];
NSLog(@"%@", p);
// This creates a new NSString, "Red Sofa", and gives it to the BNRItem
[p setItemName:@"Red Sofa"];
 

Build and run the application. Notice that the console spits out the following messages:

 
Item (): Worth $0, recorded on 2011-07-19 18:56:42 +0000
Red Sofa (A1B2C): Worth $100, recorded on 2011-07-19 18:56:42 +0000
 

Now replace the code that initializes the
BNRItem
and the code sets its instance variables with a single message send using the designated initializer. Also, get rid of the code that populates the
NSMutableArray
with strings and prints them to the console. In
main.m
, make the following changes:

 
#import
#import "BNRItem.h"
int main (int argc, const char * argv[])
{
    @autoreleasepool {
        NSMutableArray *items = [[NSMutableArray alloc] init];
        
[items addObject:@"One"];
        
[items addObject:@"Two"];
        
[items addObject:@"Three"];
        
[items insertObject:@"Zero" atIndex:0];
        
for (int i = 0; i < [items count]; i++) {
            
NSLog(@"%@", [items objectAtIndex:i]);
        
}
        
BNRItem *p = [[BNRItem alloc] init];
        
NSLog(@"%@ %@ %@ %d", [p itemName], [p dateCreated],
                              
[p serialNumber], [p valueInDollars]);
        
        BNRItem *p = [[BNRItem alloc] initWithItemName:@"Red Sofa"
                                        valueInDollars:100
                                          serialNumber:@"A1B2C"];
        NSLog(@"%@", p);
        items = nil;
    }
    return 0;
}

Build and run the application. Notice that the console now prints a single
BNRItem
that was instantiated with the values passed to
BNRItem
’s designated initializer.

 
Class methods

Methods come in two flavors: instance methods and class methods.
Instance methods
(like
init
) are sent to instances of the class, and
class methods
(like
alloc
) are sent to the class itself. Class methods typically either create new instances of the class or retrieve some global property of the class. Class methods do not operate on an instance or have any access to instance variables.

 

Syntactically, class methods differ from instance methods by the first character in their declaration. An instance method uses the
-
character just before the return type, and a class method uses the
+
character.

 

One common use for class methods is to provide convenient ways to create instances of that class. For the
BNRItem
class, it would be nice if you could create a random item so that you could test your class without having to think up a bunch of clever names. In
BNRItem.h
, declare a class method that will create a random item.

 
@interface BNRItem : NSObject
{
    NSString *itemName;
    NSString *serialNumber;
    int valueInDollars;
    NSDate *dateCreated;
}
+ (id)randomItem;
- (id)initWithItemName:(NSString *)name
        valueInDollars:(int)value
          serialNumber:(NSString *)sNumber;

Notice the order of the declarations for the methods. Class methods come first, followed by initializers, followed by any other methods. This is a convention that makes your header files easier to read.

 

In
BNRItem.m
, implement
randomItem
to create, configure, and return a
BNRItem
instance. (Again, make sure this method is between the
@implementation
and
@end
.)

 
+ (id)randomItem
{
    // Create an array of three adjectives
    NSArray *randomAdjectiveList = [NSArray arrayWithObjects:@"Fluffy",
                                                             @"Rusty",
                                                             @"Shiny", nil];
    // Create an array of three nouns
    NSArray *randomNounList = [NSArray arrayWithObjects:@"Bear",
                                                        @"Spork",
                                                        @"Mac", nil];
    // Get the index of a random adjective/noun from the lists
    // Note: The % operator, called the modulo operator, gives
    // you the remainder. So adjectiveIndex is a random number
    // from 0 to 2 inclusive.
    NSInteger adjectiveIndex = rand() % [randomAdjectiveList count];
    NSInteger nounIndex = rand() % [randomNounList count];
    // Note that NSInteger is not an object, but a type definition
    // for "unsigned long"
    NSString *randomName = [NSString stringWithFormat:@"%@ %@",
                [randomAdjectiveList objectAtIndex:adjectiveIndex],
                [randomNounList objectAtIndex:nounIndex]];
    int randomValue = rand() % 100;
    NSString *randomSerialNumber = [NSString stringWithFormat:@"%c%c%c%c%c",
                                        '0' + rand() % 10,
                                        'A' + rand() % 26,
                                        '0' + rand() % 10,
                                        'A' + rand() % 26,
                                        '0' + rand() % 10];
    BNRItem *newItem =
        [[self alloc] initWithItemName:randomName
                        valueInDollars:randomValue
                          serialNumber:randomSerialNumber];
    return newItem;
}
 

This method creates two arrays using the method
arrayWithObjects:
. This method takes a list of objects terminated by
nil
.
nil
is not added to the array; it just indicates the end of the argument list.

 

Then
randomItem
creates a string from a random adjective and noun, a random integer value, and another string from random numbers and letters. It then creates an instance of
BNRItem
and sends it the designated initializer message with these randomly-created objects and
int
as parameters.

 

In this method, you also used
stringWithFormat:
, which is a class method of
NSString
. This message is sent directly to
NSString
, and the method returns an
NSString
instance with the passed-in parameters. In Objective-C, class methods that return an object of their type (like
stringWithFormat:
and
randomItem
) are called
convenience methods
.

 

Notice the use of
self
in
randomItem
. Because
randomItem
is a class method,
self
refers to the
BNRItem
class itself instead of an instance. Class methods should use
self
in convenience methods instead of their class name so that a subclass can be sent the same message. In this case, if you create a subclass of
BNRItem
, you can send that subclass the message
randomItem
. Using
self
(instead of
BNRItem
) will allocate an instance of the class that was sent the message and set the instance’s
isa
pointer to that class.

 

Other books

A Superior Man by Paul Yee
Swim Back to Me by Ann Packer
After by Kristin Harmel
Mile Zero by Sanchez, Thomas
Avoid by Viola Grace
The Manor by Scott Nicholson