Objective-C - iOS Programming: The Big Nerd Ranch Guide (2014)

iOS Programming: The Big Nerd Ranch Guide (2014)

2. Objective-C

iOS applications are written in the Objective-C language using the Cocoa Touch frameworks. Objective-C is an extension of the C language, and the Cocoa Touch frameworks are collections of Objective-C classes.

In this chapter, you will learn the basics of Objective-C and create an application called RandomItems. Even if you are familiar with Objective-C, you should still go through this chapter to create the BNRItem class that you will use later in the book.

This book assumes you know some C and understand the basic ideas of object-oriented programming. If C or object-oriented programming makes you uneasy, we recommend starting with Objective-C Programming: The Big Nerd Ranch Guide.

Objects

Let’s say you need a way to represent a party. Your party has a few attributes that are unique to it, like a name, a date, and a list of invitees. You can also ask the party to do things, like send an email reminder to all the invitees, print name tags, or cancel the party altogether.

In C, you would define a structure to hold the data that describes a party. The structure would have data members – one for each of the party’s attributes. Each data member would have a name and a type. To create an individual party, you would use the function malloc to allocate a chunk of memory large enough to hold the structure.

In Objective-C, instead of using a structure to represent a party, you use a class. A class is like a cookie-cutter that produces objects. The Party class creates objects, and these objects are instances of the Party class. Each instance of the Party class can hold the data for a single party (Figure 2.1).

Figure 2.1 A class and its instances

A class and its instances

An instance of Party, like any object, is a chunk of data in memory, and it stores the values for its attributes in instance variables. (You also may see these referred to as “ivars” in some places.) In Objective-C, we typically put an underscore at the beginning of the instance variable name. So an instance of Party might have the instance variables _name, _date, and _budget.

A C structure is a chunk of memory, and an object is a chunk of memory. A C structure has data members, each with a name and a type. Similarly, an object has instance variables, each with a name and a type.

The important difference between a C structure and an Objective-C class is that a class has methods. A method is similar to a function: it has a name, a return type, and a list of parameters that it expects. A method also has access to an object’s instance variables. If you want an object to run the code in one of its methods, you send that object a message.

Using Instances

To use an instance of a class, you must have a variable that points to that object. A pointer variable stores the location of an object in memory, not the object itself. (It “points to” the object.) A variable that points to a Party object is declared like this:

Party *partyInstance;

Creating this pointer does not create a Party object – only a variable that can point to a Party object.

This variable is named partyInstance. Notice that this variable’s name does not start with an underscore; it is not an instance variable. It is meant to be a pointer to an instance of Party.

Creating objects

An object has a life span: it is created, sent messages, and then destroyed when it is no longer needed.

To create an object, you send an alloc message to a class. In response, the class creates an object in memory (on the heap, just like malloc() would) and gives you the address of the object, which you store in a variable:

Party *partyInstance = [Party alloc];

You create a pointer to an instance so that you can send messages to it. The first message you always send to a newly allocated instance is an initialization message. Although sending an alloc message to a class creates an instance, the instance is not ready for work until it has been initialized.

Party *partyInstance = [Party alloc];

[partyInstance init];

Because an object must be allocated and initialized before it can be used, you always combine these two messages in one line.

Party *partyInstance = [[Party alloc] init];

Combining two messages in a single line of code is called a nested message send. The innermost brackets are evaluated first, so the message alloc is sent to the class Party first. This returns a pointer to a new, uninitialized instance of Party that is then sent the message init. This returns a pointer to the initialized instance that you store in your pointer variable.

Sending messages

What do you do with an instance that has been initialized? You send it more messages.

First, let’s take a closer look at message anatomy. A message is always contained in square brackets. Within a pair of square brackets, a message has three parts:

receiver

a pointer to the object being asked to execute a method

selector

the name of the method to be executed

arguments

the values to be supplied as the parameters to the method

For example, a party might have a list of attendees that you can add to by sending the party the message addAttendee:.

[partyInstance addAttendee:somePerson];

Sending the addAttendee: message to partyInstance (the receiver) triggers the addAttendee: method (named by the selector) and passes in somePerson (an argument).

The addAttendee: message has one argument. Objective-C methods can take a number of arguments or none at all. The message init, for instance, has no arguments.

An attendee to a party might need to RSVP and inform the host what dish the attendee will bring. Thus, the Party class may have another method named addAttendee:withDish:. This message takes two arguments: the attendee and the dish. Each argument is paired with a label in the selector, and each label ends with a colon. The selector is all of the labels taken together (Figure 2.2).

Figure 2.2 Parts of a message send

Parts of a message send

The pairing of labels and arguments is an important feature of Objective-C. In other languages, this method might look something like this:

partyInstance.addAttendeeWithDish(somePerson, deviledEggs);

In those languages, it is not completely obvious what each of the arguments sent to this function are. In Objective-C, however, each argument is paired with the appropriate label.

[partyInstance addAttendee:somePerson withDish:deviledEggs];

It takes some getting used to, but eventually Objective-C programmers appreciate the clarity of arguments being interposed into the selector. The trick is to remember that for every pair of square brackets, there is only one message being sent. Even though addAttendee:withDish: has two labels, it is still only one message, and sending that message results in only one method being executed.

In Objective-C, the name of a method is what makes it unique. Therefore, a class cannot have two methods with the same name. Two method names can contain the same individual label, as long as the name of each method differs as a whole. For example, our Party class has two methods,addAttendee: and addAttendee:withDish:. These are two distinct methods, and they do not share any code.

Also, notice the distinction being made between a message and a method: a method is a chunk of code that can be executed, and a message is the act of asking a class or object to execute a method. The name of a message always matches the name of the method to be executed.

Destroying objects

To destroy an object, you set the variable that points to it to nil.

partyInstance = nil;

This line of code destroys the object pointed to by the partyInstance variable and sets the value of the partyInstance variable to nil. (It is actually a bit more complicated than that, and you will learn about memory management in the next chapter.)

The value nil is the zero pointer. (C programmers know it as NULL. Java programmers know it as null.) A pointer that has a value of nil is typically used to represent the absence of an object. For example, a party could have a venue. While the organizer of the party is still determining where to host the party, venue would point to nil. This allows you to do things like:

if (venue == nil) {

[organizer remindToFindVenueForParty];

}

Objective-C programmers typically use the shorthand form of determining if a pointer is nil:

if (!venue) {

[organizer remindToFindVenueForParty];

}

Since the ! operator means “not,” this reads as “if there is not a venue” and will evaluate to true if venue is nil.

If you send a message to a variable that is nil, nothing happens. In other languages, sending a message to the zero pointer is illegal, so you see this sort of thing a lot:

// Is venue non-nil?

if (venue) {

[venue sendConfirmation];

}

In Objective-C, this check is unnecessary because a message sent to nil is ignored. Therefore, you can simply send a message without a nil-check:

[venue sendConfirmation];

If the venue has not yet been chosen, you will not send a confirmation anywhere. (A corollary: if your program is not doing anything when you think it should be doing something, an unexpected nil pointer is often the culprit.)

Enough theory. Time for some practice and a new project.

Beginning RandomItems

This new project is not an iOS application; it is a command-line program. Building a command line program will let you focus on Objective-C without the distraction of a user interface. Still, the concepts that you will learn here and in Chapter 3 are critical for iOS development. You will get back to building iOS applications in Chapter 4.

Open Xcode and select File → New → Project.... In the lefthand table in the OS X section, click Application and then select Command Line Tool from the upper panel, as shown in Figure 2.3. Then click Next.

Figure 2.3 Creating a command line tool

Creating a command line tool

On the next sheet, name the product RandomItems and choose Foundation as its type (Figure 2.4).

Figure 2.4 Naming the project

Naming the project

Click Next and you will be prompted to save the project. Save it some place safe – you will be reusing parts of this code in future projects.

In the initial version of RandomItems, you will create an array of four strings. An array is an ordered list of pointers to other objects. Pointers in an array are accessed by an index. (Other languages might call it a list or a vector.) Arrays are zero-based; the first item in the array is always at index0.

After the array is created, you will loop through the array and print each string. Its output will appear in Xcode’s console:

Figure 2.5 Console output

Console output

Your program will need five objects: one instance of NSMutableArray and four instances of NSString (Figure 2.6).

Figure 2.6 NSMutableArray instance containing pointers to NSString instances

NSMutableArray instance containing pointers to NSString instances

In Objective-C, an array does not contain the objects that belong to it; instead it holds a pointer to each object. When an object is added to an array, the address of that object in memory is stored inside the array.

Now let’s consider the NSMutableArray and NSString classes. NSMutableArray is a subclass of NSArray. Classes exist in a hierarchy, and every class has exactly one superclass – except for the root class of the entire hierarchy: NSObject. A class inherits the behavior of its superclass.

Figure 2.7 shows the class hierarchy for NSMutableArray and NSString along with a few methods implemented by each class.

Figure 2.7 Class hierarchy

Class hierarchy

As the top superclass, NSObject’s role is to implement the basic behavior of every object in Cocoa Touch. Every class inherits the methods and instance variables defined in NSObject. Two methods that NSObject implements are alloc and init (Figure 2.7). Thus, these methods can be used to create an instance of any class.

A subclass adds methods and instance variables to extend the behavior of its superclass:

· NSString adds behavior for storing and handling strings, including the method length that returns the number of characters in a string.

· NSArray adds behavior for ordered lists, including accessing an object at a given index (objectAtIndex:) and getting the number of objects in an array (count).

· NSMutableArray extends NSArray’s abilities by adding the abilities to add and remove pointers.

Creating and populating an array

Let’s use these classes in some real code. In the project navigator, find and select the file named main.m. When it opens, you will see that some code has been written for you – most notably, a main function that is the entry point of any C or Objective-C application.

In main.m, delete the line of code that NSLogs “Hello, World!” and replace it with code that creates an instance of NSMutableArray, adds a total of four objects to the mutable array, and then destroys the array.

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])

{

@autoreleasepool {

// insert code here...

NSLog(@"Hello, World!");

// Create a mutable array object, store its address in items variable

NSMutableArray *items = [[NSMutableArray alloc] init];

// Send the message addObject: to the NSMutableArray pointed to

// by the variable items, passing a string each time

[items addObject:@"One"];

[items addObject:@"Two"];

[items addObject:@"Three"];

// Send another message, insertObject:atIndex:, to that same array object

[items insertObject:@"Zero" atIndex:0];

// Destroy the mutable array object

items = nil;

}

return 0;

}

The objects that you have added and inserted into the array are instances of NSString. You can create an instance of NSString by prefixing a character string with an @ symbol:

NSString *myString = @"Hello, World!";

Iterating over an array

Now that you have an items array with strings in it, you are going to iterate over the array, access each string, and output it to the console.

You could write this operation as a for loop:

for (int i = 0; i < [items count]; i++) {

NSString *item = [items objectAtIndex:i];

NSLog(@"%@", item);

}

Because arrays are zero-based, the counter starts at 0 and stops at one less than the result of sending the items array the message count. Within the loop, you then send the message objectAtIndex: to retrieve the object at the current index before printing it out.

The information returned from count is important because if you ask an array for an object at an index that is equal to or greater than the number of objects in the array, an exception will be thrown. (In some languages, exceptions are thrown and caught all the time. In Objective-C, exceptions are considered programmer errors and should be fixed in code instead of handled at runtime. We will talk more about exceptions at the end of this chapter.)

This code would work fine, but Objective-C provides a better option for iterating over an array called fast enumeration. Fast enumeration is shorter syntax than a traditional for loop and far less error-prone. In some cases, it will be faster.

In main.m, add the following code that uses fast enumeration to iterate over the items array.

int main (int argc, const char * argv[])

{

@autoreleasepool {

// Create a mutable array object, store its address in items variable

NSMutableArray *items = [[NSMutableArray alloc] init];

// Send the message addObject: to the NSMutableArray pointed to

// by the variable items, passing a string each time

[items addObject:@"One"];

[items addObject:@"Two"];

[items addObject:@"Three"];

// Send another message, insertObject:atIndex:, to that same array object

[items insertObject:@"Zero" atIndex:0];

// For every item in the items array ...

for (NSString *item in items) {

// Log the description of item

NSLog(@"%@", item);

}

items = nil;

}

return 0;

}

Fast enumeration has a limitation: you cannot use it if you need to add or remove objects within the loop. Trying to do so will cause an exception to be thrown. If you need to add or remove objects, you must use a regular for loop with a counter variable.

Build and run the application (Command-R). A new pane will appear at the bottom of the Xcode window. This is the debug area. On the righthand side of this area is the console and your output.

Figure 2.8 Output in console

Output in console

If you need to, you can resize the debug area and its panels by dragging their frames. (In fact, you can resize any area in the workspace window this way.)

You have completed the first phase of the RandomItems program. Before you continue with the next phase, let’s take a closer look at the NSLog function and format strings.

Format strings

NSLog() takes a variable number of arguments and prints a string to the log. In Xcode, the log appears in the console. The first argument of NSLog() is required. It is an instance of NSString and is called the format string.

A format string contains text and a number of tokens. Each token (also called a format specification) is prefixed with a percent symbol (%). Each additional argument passed to the function replaces a token in the format string.

Tokens specify the type of the argument that they correspond to. Here is an example:

int a = 1;

float b = 2.5;

char c = 'A';

NSLog(@"Integer: %d Float: %f Char: %c", a, b, c);

The output would be

Integer: 1 Float: 2.5 Char: A

Objective-C format strings work the same way as in C. However, Objective-C adds one more token: %@. This token corresponds to an argument whose type is “a pointer to any object.”

When %@ is encountered in the format string, instead of the token being replaced by the corresponding argument, that argument is sent the message description. The description method returns an instance of NSString that replaces the token.

Because the argument that corresponds to the %@ token is sent a message, that argument must be an object. Back in Figure 2.7, you can see that description is a method on the NSObject class. Thus, every class implements description, which is why you can use the %@ token with any object.

Subclassing an Objective-C Class

In this section, you are going to create a new class named BNRItem. BNRItem will be a subclass of NSObject.

Figure 2.9 Class hierarchy including BNRItem

Class hierarchy including BNRItem

An instance of BNRItem will represent something that a person owns in the real world, like a laptop, a bicycle, or a backpack. In terms of Model-View-Controller, BNRItem is a model class. An instance of BNRItem stores information about a possession.

Once you have created the BNRItem class, you will populate the items array with instances of BNRItem instead of NSString.

Figure 2.10 A different class of items

A different class of items

Later in the book, you will reuse BNRItem in a complex iOS application.

Creating an NSObject subclass

To create a new class in Xcode, choose File → New → File.... In the lefthand table of the panel that appears, select Cocoa from the OS X section. Then select Objective-C class from the upper panel and click Next (Figure 2.11).

Figure 2.11 Creating a class

Creating a class

On the next panel, name this new class BNRItem and enter NSObject as its superclass, as shown in Figure 2.12.

Figure 2.12 Choosing a superclass

Choosing a superclass

Click Next and you will be asked where to save the files for this class. The default location is fine. Make sure the checkbox is selected for the RandomItems target. Click Create.

In the project navigator, find the class files for BNRItem ߝ BNRItem.h and BNRItem.m:

· BNRItem.h is the header file (also called the interface file). This file declares the name of the new class, its superclass, the instance variables that each instance of this class has, and any methods this class implements.

· BNRItem.m is the implementation file, and it contains the code for the methods that the class implements.

Every Objective-C class has these two files. You can think of the header file as a user manual for an instance of a class and the implementation file as the engineering details that define how it really works.

Select BNRItem.h in the project navigator. The contents of the file look like this:

#import <Foundation/Foundation.h>

@interface BNRItem : NSObject

@end

To declare a class in Objective-C, you use the keyword @interface followed by the name of the new class. After a colon comes the name of the superclass. Objective-C only allows single inheritance, so every class can only have one superclass:

@interface ClassName : SuperclassName

The @end directive indicates that the class declaration has come to an end.

Notice the @ prefixes. Objective-C retains the keywords of the C language. Additional keywords specific to Objective-C are distinguishable by the @ prefix.

Instance variables

An “item,” in our world, is going to have a name, a serial number, a value, and a date of creation. These will be the instance variables of BNRItem.

Instance variables for a class are declared between curly braces immediately after the class declaration.

In BNRItem.h, add a set of curly braces and four instance variables to the BNRItem class:

#import <Foundation/Foundation.h>

@interface BNRItem : NSObject

{

NSString *_itemName;

NSString *_serialNumber;

int _valueInDollars;

NSDate *_dateCreated;

}

@end

Now every instance of BNRItem will have one spot for a simple integer and three spots for pointers to objects, specifically two NSString instances and one NSDate instance. (Remember, the * denotes that the variable is a pointer.) Figure 2.13 shows an example of a BNRItem instance after its instance variables have been given values.

Figure 2.13 A BNRItem instance

A BNRItem instance

Notice that Figure 2.13 shows a total of four objects: the instance of BNRItem, two instances of NSString, and an instance of NSDate. Each object exists independently and outside of the others. The BNRItem object’s instance variables are the pointers to the other objects, not the objects themselves.

For example, every BNRItem instance has a pointer instance variable named _itemName. The _itemName of the BNRItem object shown in Figure 2.13 points to an NSString object whose contents are "Red Sofa". The "Red Sofa" string does not live inside the BNRItem object. The BNRItem object only knows where the "Red Sofa" string lives in memory and stores that address as _itemName. One way to think of this relationship is “the BNRItem object calls this string its _itemName. ”

The story is different for the instance variable _valueInDollars. This instance variable is not a pointer to another object; it is just an int. The int itself does live inside the BNRItem object.

The idea of pointers is not easy to understand at first. In the next chapter, you will learn more about objects, pointers, and instance variables, and throughout this book you will see object diagrams like Figure 2.13 to drive home the difference between an object and a pointer to an object.

Accessing instance variables

Now that instances of BNRItem have instance variables, you need a way to get and set their values. In object-oriented languages, we call methods that get and set instance variables accessors. Individually, we call them getters and setters. Without these methods, an object cannot access the instance variables of another object.

In BNRItem.h, declare accessor methods for the instance variables of the BNRItem class. You need getters and setters for _valueInDollars, _itemName, and _serialNumber. The _dateCreated instance variable will be read-only, so it only needs a getter method.

#import <Foundation/Foundation.h>

@interface BNRItem : NSObject

{

NSString *_itemName;

NSString *_serialNumber;

int _valueInDollars;

NSDate *_dateCreated;

}

- (void)setItemName:(NSString *)str;

- (NSString *)itemName;

- (void)setSerialNumber:(NSString *)str;

- (NSString *)serialNumber;

- (void)setValueInDollars:(int)v;

- (int)valueInDollars;

- (NSDate *)dateCreated;

@end

In Objective-C, the name of a setter method is set plus the capitalized name of the instance variable it is changing – in this case, setItemName:. In other languages, the name of the getter method would likely be getItemName. However, in Objective-C, the name of the getter method is just the name of the instance variable. Some of the cooler parts of the Cocoa Touch library make the assumption that your classes follow this convention; therefore, stylish Cocoa Touch programmers always do so.

(For those of you with some experience in Objective-C, we will talk about properties in the next chapter.)

Next, open BNRItem’s implementation file, BNRItem.m.

At the top of any implementation file, the header file of that class is always imported. The implementation of a class needs to know how it has been declared. (Importing a file is the same as including a file in C except that it ensures that the file will only be included once.)

After the import statement is the implementation block that begins with the @implementation keyword followed by the name of the class that is being implemented. All of the method definitions in the implementation file are inside this implementation block. Methods are defined until you close out the block with the @end keyword.

In BNRItem.m, delete anything that the template may have added between @implementation and @end. Then define the accessor methods for the instance variables that you declared in BNRItem.h.

#import "BNRItem.h"

@implementation BNRItem

- (void)setItemName:(NSString *)str

{

_itemName = str;

}

- (NSString *)itemName

{

return _itemName;

}

- (void)setSerialNumber:(NSString *)str

{

_serialNumber = str;

}

- (NSString *)serialNumber

{

return _serialNumber;

}

- (void)setValueInDollars:(int)v

{

_valueInDollars = v;

}

- (int)valueInDollars

{

return _valueInDollars;

}

- (NSDate *)dateCreated

{

return _dateCreated;

}

@end

Notice that each setter method sets the instance variable to whatever is passed in as an argument, and each getter method returns the value of the instance variable.

At this point, check for and fix any errors in your code that Xcode is warning you about. Some possible culprits are typos and missing semicolons.

Let’s test out your new class and its accessor methods. In main.m, first import the header file for the BNRItem class.

#import <Foundation/Foundation.h>

#import "BNRItem.h"

int main (int argc, const char * argv[])

{

...

Why do you import the class header BNRItem.h but not, say, NSMutableArray.h? The NSMutableArray class comes from the Foundation framework, so it is included when you import Foundation/Foundation.h. On the other hand, the BNRItem class exists in its own file, so you have to explicitly import it into main.m. If you do not, the compiler will not know that it exists and will complain loudly.

Next, create an instance of BNRItem and log its instance variables to the console.

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 every item in the array pointed to by items...

for (NSString *item in items) {

// print a description

NSLog(@"%@", item);

}

BNRItem *item = [[BNRItem alloc] init];

NSLog(@"%@ %@ %@ %d", [item itemName], [item dateCreated],

[item serialNumber], [item valueInDollars]);

items = nil;

}

return 0;

}

Build and run the application. At the end of your output, you will see three (null) strings and a 0. These are the values of the instance variables of your freshly-minted instance of BNRItem.

Figure 2.14 Instance variables’ values in console

Instance variables’ values in console

When an object is created, all of its instance variables are “zeroed-out.” A pointer to an objects points to nil; a primitive like int has the value of 0.

To give the BNRItem object’s instance variables more interesting values, you need to create new objects and pass them as arguments to the setter methods.

In main.m, type in the following code:

// Notice we are omitting some of the surrounding code

...

BNRItem *item = [[BNRItem alloc] init];

// This creates an NSString, "Red Sofa" and gives it to the BNRItem

[item setItemName:@"Red Sofa"];

// This creates an NSString, "A1B2C" and gives it to the BNRItem

[item setSerialNumber:@"A1B2C"];

// This sends the value 100 to be used as the valueInDollars of this BNRItem

[item setValueInDollars:100];

NSLog(@"%@ %@ %@ %d", [item itemName], [item dateCreated],

[item serialNumber], [item valueInDollars]);

...

Build and run the application. You will see the values of the three instance variables. You will still see (null) for dateCreated. Later in the chapter, you will take care of giving this instance variable a value when an object is created.

Figure 2.15 More interesting values

More interesting values

Using dot syntax

To get and set an instance variable, you can send explicit accessor messages:

BNRItem *item = [[BNRItem alloc] init];

// set valueInDollars by sending an explicit message

[item setValueInDollars:5];

// get valueInDollars by sending an explicit message

int value = [item valueInDollars];

Or you can use dot syntax, also called dot notation. Here is the same code using dot syntax:

BNRItem *item = [[BNRItem alloc] init];

// set valueInDollars using dot syntax

item.valueInDollars = 5;

// get valueInDollars using dot syntax

int value = item.valueInDollars;

The receiver (item) is followed by a . followed by the name of the instance variable without the leading underscore (valueInDollars).

Notice that the syntax is the same for both setting and getting the instance variable (item.valueInDollars); the difference is in which side of the assignment operator it is on.

There is no difference at runtime between accessor messages and dot syntax; the compiled code is the same and either syntax will invoke the valueInDollars and setValueInDollars: methods that you just implemented.

These days, stylish Objective-C programmers tend to use dot syntax for invoking accessors. It makes code easier to read, especially when there would traditionally be nested message calls. It is also consistent with Apple’s code. It is what we will do in this book.

In main.m, update your code to use dot syntax to set the instance variables and to get them as part of the format string.

...

BNRItem *item = [[BNRItem alloc] init];

// This creates an NSString, "Red Sofa" and gives it to the BNRItem

[item setItemName:@"Red Sofa"];

item.itemName=@"Red Sofa";

// This creates an NSString, "A1B2C" and gives it to the BNRItem

[item setSerialNumber:@"A1B2C"];

item.serialNumber=@"A1B2C";

// This sends the value 100 to be used as the valueInDollars of this BNRItem

[item setValueInDollars:100];

item.valueInDollars = 100;

NSLog(@"%@ %@ %@ %d", [item itemName], [item dateCreated],

[item serialNumber], [item valueInDollars]);

NSLog(@"%@ %@ %@ %d", item.itemName, item.dateCreated,

item.serialNumber, item.valueInDollars);

...

Class vs. instance methods

Methods come in two types: instance methods and class methods. A class method typically either creates a new instance of the class or retrieves some global property of the class. An instance method operates on a particular instance of the class. For instance, the accessors that you just implemented are all instance methods. You use them to set or get the instance variables of a particular object.

To invoke an instance method, you send the message to an instance of the class. To invoke a class method, you send the message to the class itself.

For example, when you created an instance of BNRItem, you sent alloc (a class method) to the BNRItem class and then init (an instance method) to the instance of BNRItem returned from alloc.

The description method is an instance method. In the next section, you are going to implement description in BNRItem to return an NSString object that describes an instance of BNRItem. Later in the chapter, you will implement a class method to create an instance of BNRItem using random values.

Overriding methods

A subclass can also override methods of its superclass. For example, sending the description message to an instance of NSObject returns the object’s class and its address in memory as an instance of NSString that looks like this:

<BNRQuizViewController: 0x4b222a0>

A subclass of NSObject can override this method to return an NSString object that better describes an instance of that subclass. For example, the NSString class overrides description to return the string itself. The NSArray class overrides description to return the description of every object in the array.

Because BNRItem is a subclass of NSObject (the class that originally declares the description method), when you re-implement description in BNRItem, you are overriding it.

When overriding a method, all you need to do is define it in the implementation file; you do not need to declare it in the header file because it has already been declared by the superclass.

In BNRItem.m, override the description method. The code for a method implementation can go anywhere between @implementation and @end, as long as it is not inside the curly braces of an existing method.

- (NSString *)description

{

NSString *descriptionString =

[[NSString alloc] initWithFormat:@"%@ (%@): Worth $%d, recorded on %@",

self.itemName,

self.serialNumber,

self.valueInDollars,

self.dateCreated];

return descriptionString;

}

Note what you are not doing here: you are not passing the instance variables by name (e.g.., _itemName). Instead you are invoking accessors (via dot syntax). It is good practice to use accessor methods to access instance variables even inside a class. It is possible that an accessor method may change something about the instance variable that you are trying to access, and you want to make sure it gets the chance to do what it needs to.

Now whenever you send the message description to an instance of BNRItem, it will return an instance of NSString that better describes the instance.

In main.m, replace the statement that prints out the instance variables individually with a statement that relies on BNRItem’s implementation of description.

...

item.valueInDollars = 100;

NSLog(@"%@ %@ %@ %d", item.itemName, item.dateCreated,

item.serialNumber, item.valueInDollars);

// The %@ token is replaced with the result of sending

// the description message to the corresponding argument

NSLog(@"%@", item);

items = nil;

Build and run the application and check your results in the console.

Figure 2.16 An instance of BNRItem described

An instance of BNRItem described

What if you want to create an entirely new instance method, one that you are not overriding from the superclass? You declare the new method in the header file and define it in the implementation file. Let’s see how that works by creating two new instance methods to initialize an instance ofBNRItem.

Initializers

Right now, the BNRItem class has only one way to initialize an instance – the init method, which it inherits from the NSObject class. In this section, you are going to write two additional initialization methods, or initializers, for BNRItem.

In BNRItem.h, declare two initializers.

NSDate *_dateCreated;

}

- (instancetype)initWithItemName:(NSString *)name

valueInDollars:(int)value

serialNumber:(NSString *)sNumber;

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

- (void)setItemName:(NSString *)str;

(Wondering about instancetype? Hold on – we will get there shortly.)

Each initializer begins with the word init. Naming initializers this way does not make these methods different from other instance methods; it is only a naming convention. However, the Objective-C community is all about naming conventions, which you should strictly adhere to. (Disregarding naming conventions in Objective-C results in problems that are worse than you might imagine.)

An initializer takes arguments that the object can use to initialize itself. Often, a class has multiple initializers because instances can have different initialization needs. For instance, the first initializer that you declared takes three arguments that it uses to configure the item’s name, value, and serial number. So you need all of this information to initialize an instance with this method. What if you only know the item’s name? Then you can use the second initializer.

The designated initializer

For each class, regardless of how many initialization methods there are, one method is chosen as the designated initializer. The designated initializer makes sure that every instance variable of an object is valid. (“Valid” in this context means “when you send messages to this object after initializing it, you can predict the outcome and nothing bad will happen.”)

Typically, the designated initializer has parameters for the most important and frequently used instance variables of an object. The BNRItem class has four instance variables, but only three are writeable. Therefore, BNRItem’s designated initializer should accept three arguments and provide a value within its implementation for _dateCreated.

In BNRItem.h, add a comment naming the designated initializer:

NSDate *_dateCreated;

}

// Designated initializer for BNRItem

- (instancetype)initWithItemName:(NSString *)name

valueInDollars:(int)value

serialNumber:(NSString *)sNumber;

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

- (void)setItemName:(NSString *)str;

instancetype

The return type for both initializers is instancetype. This keyword can only be used for return types, and it matches the return type to the receiver. init methods are always declared to return instancetype.

Why not make the return type BNRItem *? That would cause a problem if the BNRItem class was ever subclassed. The subclass would inherit all of the methods from BNRItem, including this initializer and its return type. If an instance of the subclass was sent this initializer message, what would be returned? Not a pointer to a BNRItem instance, but a pointer to an instance of the subclass. You might think, “No problem. I will override the initializer in the subclass to change the return type.” But in Objective-C, you cannot have two methods with the same selector and different return types (or arguments). By specifying that an initialization method returns “an instance of the receiving object,” you never have to worry what happens in this situation.

id

Before the instancetype keyword was introduced in Objective-C, initializers returned id (pronounced “eye-dee”). This type is defined as “a pointer to any object.” (id is a lot like void * in C.). As of this writing, Xcode class templates still use id as the return type of initializers added in boilerplate code. We imagine that this will change soon.

Unlike instancetype, id can be used as more than just a return type. You can declare variables or method parameters of type id when you are unsure what type of object the variable will end up pointing to.

id objectOfUnknownType;

You can use id when using fast enumeration to iterate over an array of multiple or unknown types of objects:

for (id item in items) {

NSLog(@"%@", item);

}

Note that because id is defined as “a pointer to any object,” you do not include an * when declaring a variable or method parameter of this type.

Implementing the designated initializer

In BNRItem.m, implement the designated initializer within the implementation block.

@implementation BNRItem

- (instancetype)initWithItemName:(NSString *)name

valueInDollars:(int)value

serialNumber:(NSString *)sNumber

{

// Call the superclass's designated initializer

self = [super init];

// Did the superclass's designated initializer succeed?

if (self) {

// Give the instance variables initial values

_itemName = name;

_serialNumber = sNumber;

_valueInDollars = value;

// Set _dateCreated to the current date and time

_dateCreated = [[NSDate alloc] init];

}

// Return the address of the newly initialized object

return self;

}

There is a lot to talk about in this code. First, notice that you set the _dateCreated instance variable to point a new instance of NSDate, which represents the current date and time.

Next, consider the first line of code in this implementation. In the designated initializer, the first thing you always do is call the superclass’s designated initializer using super. The last thing you do is return a pointer to the successfully initialized object using self. So to understand what is going on in an initializer, you need to know about self and super.

self

Inside a method, self is an implicit local variable. There is no need to declare it, and it is automatically set to point to the object that was sent the message. (Most object-oriented languages have this concept, but some call it this instead of self.) Typically, self is used so that an object can send a message to itself:

- (void)chickenDance

{

[self pretendHandsAreBeaks];

[self flapWings];

[self shakeTailFeathers];

}

In the last line of an init method, you always return the newly initialized object so that the caller can assign it to a variable:

return self;

super

When you are overriding a method, you often want to keep what the method of the superclass is doing and have your subclass add something new on top of that. To make this easier, there is a compiler directive in Objective-C called super:

- (void)someMethod

{

[super someMethod];

[self doMoreStuff];

}

How does super work? Usually when you send a message to an object, the search for a method of that name starts in the object’s class. If there is no such method, the search continues in the superclass of the object. The search will continue up the inheritance hierarchy until a suitable method is found. (If it gets to the top of the hierarchy and no method is found, an exception is thrown.)

When you send a message to super, you are sending a message to self, but the search for the method skips the object’s class and starts at the superclass. In the case of BNRItem’s designated initializer, you send the init message to super. This calls NSObject’s implementation of init.

Confirming initialization success

Now let’s look at the next line where you confirm what the superclass’s initializer returned. If an initializer message fails, it will return nil. Therefore, it is a good idea to save the return value of the superclass’s initializer into the self variable and confirm that it is not nil before doing any further initialization.

Instance variables in initializers

Now we get to the core of this method where the instance variables are given values. Earlier we told you not to access instance variables directly and to use accessor methods. Now we are asking you to break that rule when writing initializers.

While an initializer is being executed, the object is being born, and you cannot be sure that its instance variables have all been set to usable values. When writing a method, you typically assume that all of an object’s instance variables have been set to usable values. Thus, invoking a method (like an accessor) at a time when this may not be the case is unsafe. At Big Nerd Ranch, we typically set the instance variables directly in initializers, instead of calling accessor methods.

Some very good Objective-C programmers do use accessors in initializers. They argue that if the accessor does something complicated, you want that code in exactly one place; replicating it in your initializer is bad. We are not religiously devoted to either approach, but in this book we will set instance variables directly in initializers.

Other initializers and the initializer chain

Let’s implement the second initializer for the BNRItem class. In this initializer’s definition, you are not going to replicate the code in the designated initializer. Instead, this initializer will simply call the designated initializer, passing the information it is given for the _itemName and default values for the other arguments.

In BNRItem.m, implement initWithItemName:.

- (instancetype)initWithItemName:(NSString *)name

{

return [self initWithItemName:name

valueInDollars:0

serialNumber:@""];

}

The BNRItem class already has a third initializer – init, which it inherits from NSObject. If init is used to initialize an instance of BNRItem, none of the stuff that you put in the designated initializer will happen. Therefore, you must override init in BNRItem to link to BNRItem’s designated initializer.

In BNRItem.m, override init to call initWithItemName:, passing a default value for the item’s name.

- (instancetype)init

{

return [self initWithItemName:@"Item"];

}

Now when init is sent to an instance of BNRItem, the method will call initWithItemName: with a default value for _itemName, which will call the designated initializer, initWithItemName:valueInDollars:serialNumber: with default values for _valueInDollars and _serialNumber.

The relationships between BNRItem’s initializers are shown in Figure 2.17; the designated initializers are white, and the additional initializers are gray.

Figure 2.17 A chain of initializers

A chain of initializers

Using initializers in a chain reduces the possibility of error and makes maintaining code easier. The programmer who created the class makes it clear which initializer is the designated initializer. You only write the core of the initializer once in the designated initializer, and other initialization methods simply call the designated initializer (directly or indirectly) with default values.

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 (directly or indirectly) before doing anything else.

· Any other initializers call the class’s designated initializer (directly or indirectly).

· 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 (directly or indirectly).

Using initializers

Now that you have a designated initializer for BNRItem, you can use it instead of setting instance variables individually.

In main.m, remove the creation of the single BNRItem instance and all three setter messages. Then add code that creates an instance and sets its instance variables using the designated initializer.

...

// For every item in the items array ...

for (NSString *item in items) {

// ... print a description of the current item

NSLog(@"%@", item);

}

BNRItem *item = [[BNRItem alloc] init];

item.itemName = @"Red Sofa";

item.serialNumber = @"A1B2C";

item.valueInDollars = 100;

BNRItem *item = [[BNRItem alloc] initWithItemName:@"Red Sofa"

valueInDollars:100

serialNumber:@"A1B2C"];

NSLog(@"%@", item);

...

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

Let’s confirm that your other two initializers work as expected. In main.m, create two additional instances of BNRItem using initWithItemName: and init.

...

BNRItem *item = [[BNRItem alloc] initWithItemName:@"Red Sofa"

valueInDollars:100

serialNumber:@"A1B2C"];

NSLog(@"%@", item);

BNRItem *itemWithName = [[BNRItem alloc] initWithItemName:@"Blue Sofa"];

NSLog(@"%@", itemWithName);

BNRItem *itemWithNoName = [[BNRItem alloc] init];

NSLog(@"%@", itemWithNoName);

items = nil;

}

return 0;

}

Build and run the application and check the console to confirm that BNRItem’s initialization chain is working.

Figure 2.18 Three initializers at work

Three initializers at work

There is only one thing left to do to complete the BNRItem class. You are going to write a method that creates an instance and initializes it with random values. This method will be a class method.

Class methods

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.

In BNRItem.h, declare a class method that will create a random item.

@interface BNRItem : NSObject

{

NSString *_itemName;

NSString *_serialNumber;

int _valueInDollars;

NSDate *_dateCreated;

}

+ (instancetype)randomItem;

- (instancetype)initWithItemName:(NSString *)name

valueInDollars:(int)value

serialNumber:(NSString *)sNumber;

Notice the order of the declarations in the header file. Instance variables come first, followed by class methods, followed by initializers, followed by any other instance methods. This convention makes header files easier to read.

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

+ (instancetype)randomItem

{

// Create an immutable array of three adjectives

NSArray *randomAdjectiveList = @[@"Fluffy", @"Rusty", @"Shiny"];

// Create an immutable array of three nouns

NSArray *randomNounList = @[@"Bear", @"Spork", @"Mac"];

// 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 = arc4random() % [randomAdjectiveList count];

NSInteger nounIndex = arc4random() % [randomNounList count];

// Note that NSInteger is not an object, but a type definition

// for "long"

NSString *randomName = [NSString stringWithFormat:@"%@ %@",

[randomAdjectiveList objectAtIndex:adjectiveIndex],

[randomNounList objectAtIndex:nounIndex]];

int randomValue = arc4random() % 100;

NSString *randomSerialNumber = [NSString stringWithFormat:@"%c%c%c%c%c",

'0' + arc4random() % 10,

'A' + arc4random() % 26,

'0' + arc4random() % 10,

'A' + arc4random() % 26,

'0' + arc4random() % 10];

BNRItem *newItem = [[self alloc] initWithItemName:randomName

valueInDollars:randomValue

serialNumber:randomSerialNumber];

return newItem;

}

First, at the beginning of this method, notice the syntax for creating the two arrays randomAdjectiveList and randomNounList – an @ symbol followed by square brackets. Within the brackets is a comma-delimited list of objects that will populate the array. (In this case, the objects are instances ofNSString.) This syntax is shorthand for creating instances of NSArray. Note that it always creates an immutable array. You can only use this shorthand if you do not need the resulting array to be mutable.

After creating the arrays, randomItem creates a string from a random adjective and noun, a random integer value, and another string from random numbers and letters.

Finally, the method 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 the NSString class, and the method returns an NSString instance with the passed-in parameters. In Objective-C, class methods that return an object of their type (likestringWithFormat: 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 called BNRToxicWasteItem, you could do this:

BNRToxicWasteItem *item = [BNRToxicWasteItem randomItem];

Testing your subclass

For the final version of RandomItems in this chapter, you are going to fill the items array with 10 randomly-created instances of BNRItem. Then you will loop through the array and log each item (Figure 2.19).

Figure 2.19 Random items

Random items

In main.m, delete all of the code except for the creation and destruction of the items array. Then add 10 random BNRItem instances to the array and log them.

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 every item in the items array ...

for (NSString *item in items) {

// ... print a description of the current item

NSLog(@"%@", item);

}

BNRItem *item = [[BNRItem alloc] initWithItemName:@"Red Sofa"

valueInDollars:100

serialNumber:@"A1B2C"];

NSLog(@"%@", item);

BNRItem *itemWithName = [[BNRItem alloc] initWithItemName:@"Blue Sofa"];

NSLog(@"%@", itemWithName);

BNRItem *itemWithNoName = [[BNRItem alloc] init];

NSLog(@"%@", itemWithNoName);

for (int i = 0; i < 10; i++) {

BNRItem *item = [BNRItem randomItem];

[items addObject:item];

}

for (BNRItem *item in items) {

NSLog(@"%@", item);

}

items = nil;

}

return 0;

}

Notice that you do not use fast enumeration in the first loop because you are adding to the array within the loop.

Build and run your application and then check the output in the console.

More on NSArray and NSMutableArray

You will frequently use arrays when developing iOS applications, so let’s go over some more array-related details.

An Objective-C array can contain objects of different types. For example, although your items array currently only contains instances of BNRItem, you could add an instance of NSDate or any other Objective-C object. This is different from most strongly typed languages where an array can only hold objects of a single type.

Objective-C arrays can only hold references to Objective-C objects. Primitives and C structures cannot be added to an Objective-C array. If you need to add primitives or C structures, you can “wrap” them in Objective-C objects written for this purpose, including NSNumber, NSValue, and NSData.

Note that you cannot add nil to an array. If you need to add “holes” to an array, you must use NSNull. NSNull is a class whose only instance is meant to stand in for nil and is used specifically for this task.

[items addObject:[NSNull null]];

When accessing members of an array, you have used the objectAtIndex: message with the index of the object you want returned. This, like many other elements of Objective-C, is very verbose. Thus, there exists a shorthand syntax for accessing members of an array:

NSString *foo = items[0];

This line of code is equivalent to sending objectAtIndex: to items.

NSString *foo = [items objectAtIndex:0];

In BNRItem.m, update randomItem to use this syntax when creating the random name.

+ (instancetype)randomItem

{

...

NSString *randomName = [NSString stringWithFormat:@"%@ %@",

[randomAdjectiveList objectAtIndex:adjectiveIndex],

[randomNounList objectAtIndex:nounIndex]];

NSString *randomName = [NSString stringWithFormat:@"%@ %@",

randomAdjectiveList[adjectiveIndex],

randomNounList[nounIndex]];

int randomValue = arc4random() % 100;

...

return newItem;

}

Build and run to confirm that the program works the same as before.

The nested brackets that you end up with can make things confusing because they are used in two distinct ways: one use sends a message and the other use accesses items in an array. Sometimes, it can be clearer to stick with sending the typed-out message. Other times, it is nice to avoid typing the finger-numbing objectAtIndex:.

Whichever syntax you use, it is important to understand that there is no difference in your application: the compiler turns the shorthand syntax into code that sends the objectAtIndex: message.

In an NSMutableArray, you can use a similar shorthand syntax to add and replace objects.

NSMutableArray *items = [[NSMutableArray alloc] init];

items[0] = @"A"; // Add @"A"

items[1] = @"B"; // Add @"B"

items[0] = @"C"; // Replace @"A" with @"C"

These lines are equivalent to sending insertObject:atIndex: and replaceObjectAtIndex:withObject: messages to items.

Exceptions and Unrecognized Selectors

At runtime, when a message is sent to an object, that object goes to the class that created it and says, “I was sent this message. Run the code for the matching method.” This is different than in most compiled languages, where the method to be executed is determined at compile time.

How does an object know which class created it? It uses its isa pointer. Every object has an instance variable called isa. When an object is created, the class sets the isa instance variable of the returned object to point back at that class (Figure 2.20). It is called the isa pointer because an object“is a” instance of that class. Although you probably will never explicitly use the isa pointer, its existence gives Objective-C gets much of its power.

Figure 2.20 The isa pointer

The isa pointer

An object only responds to a message if its class (pointed to by its isa pointer) implements the associated method. Because this happens at runtime, Xcode cannot always figure out at compile time (when the application is built) whether an object will respond to a message. Xcode will give you an error if it thinks you are sending a message to an object that will not respond, but if it is not sure, it will let the application build.

If, for some reason (and there are many possibilities), you end up sending a message to an object that does not respond, your application will throw an exception. Exceptions are also known as run-time errors because they occur once your application is running, as opposed to compile-time errors that show up when your application is being built, or compiled.

To practice dealing with exceptions, you are going to cause one in RandomItems.

In main.m, get the last item in the array using the lastObject method of NSArray. Then send this item a message that it will not understand:

#import <Foundation/Foundation.h>

#import "BNRItem.h"

int main (int argc, const char * argv[])

{

@autoreleasepool {

NSMutableArray *items = [[NSMutableArray alloc] init];

for (int i = 0; i < 10; i++) {

BNRItem *item = [BNRItem randomItem];

[items addObject:item];

}

id lastObj = [items lastObject];

// lastObj is an instance of BNRItem and will not understand the count message

[lastObj count];

for (BNRItem *item in items) {

NSLog(@"%@", item);

}

items = nil;

}

return 0;

}

Build and run the application. Your application will compile, start running, and then halt. Check your console and find the line that looks like this:

2014-01-19 12:23:47.990 RandomItems[10288:707] ***

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:

'-[BNRItem count]: unrecognized selector sent to instance 0x100117280'

This is what an exception looks like. What exactly is it saying? First, it tells you the date, time, and name of the application. You can ignore that information and focus on what comes after the “***.” That line tells you that an exception occurred and the reason.

The reason is the most important piece of information an exception gives you. Here the reason tells you that an unrecognized selector was sent to an instance. You know that selector means message. You sent a message to an object, and the object does not implement that method.

The type of the receiver and the name of the message are also in this output, which makes it easier to debug. An instance of BNRItem was sent the message count. The - at the beginning tells you the receiver was an instance of BNRItem. A + would mean the class itself was the receiver.

There are two important lessons to take away from this. First, always check the console if your application halts or crashes; errors that occur at runtime (exceptions) are just as important as those that occur during compiling. Second, remember that unrecognized selector means the message you are sending is not implemented by the receiver. You will make this mistake more than once, and you will want to be able to diagnose it quickly.

Some languages use try and catch blocks to handle exceptions. While Objective-C has this ability, we do not use it very often in application code. Typically, an exception is a programmer error and should be fixed in the code instead of handled at runtime.

In main.m, remove the exception-causing code.

for (int i = 0; i < 10; i++) {

BNRItem *item = [BNRItem randomItem];

[items addObject:p];

}

id lastObj = [items lastObject];

[lastObj count];

for (BNRItem *item in items) {

NSLog(@"%@", item);

}

Challenges

Most chapters in this book will finish with at least one challenge that encourages you to take your work in the chapter one step further and prove to yourself what you have learned. We suggest that you tackle as many of these challenges as you can to cement your knowledge and move fromlearning iOS development from us to doing iOS development on your own.

Challenges come in three levels of difficulty:

· Bronze challenges typically ask you to do something very similar to what you did in the chapter. These challenges reinforce what you learned in the chapter and force you to type in similar code without having it laid out in front of you. Practice makes perfect.

· Silver challenges require you to do more digging and more thinking. You will need to use methods, classes, and properties that you have not seen before, but the tasks are still similar to what you did in the chapter.

· Gold challenges are difficult and can take hours to complete. They require you to understand the concepts from the chapter and then do some quality thinking and problem-solving on your own. Tackling these challenges will prepare you for the real-world work of iOS development.

Before beginning any challenge, always make a copy of your project directory in Finder and attack the challenge in that copy. Many chapters build on previous chapters, and working on challenges in a copy of the project assures you will be able to progress through the book.

Bronze Challenge: Bug Finding

Create a bug in your program by asking for the eleventh item in the array. Run it and note the exception that gets thrown.

Silver Challenge: Another Initializer

Create another initializer method for the BNRItem class. This initializer is not the designated initializer of BNRItem. It takes an instance of NSString that identifies the itemName of the item and an instance of NSString that identifies the serialNumber.

Gold Challenge: Another Class

Create a subclass of BNRItem named BNRContainer. An instance of BNRContainer should have an array of subitems that contains instances of BNRItem. Printing the description of a BNRContainer object should show you the name of the container, its value in dollars (a sum of all items in the container plus the value of the container itself), and a list of every instance of BNRItem it contains. A properly-written BNRContainer class can contain instances of BNRContainer. It can also report back its full value and every contained item properly.

Are You More Curious?

In addition to Challenges, many chapters will conclude with one or more “For the More Curious” sections. These sections offer deeper explanations of or additional information about the topics presented in the chapter. The knowledge in these sections is not absolutely essential to get you where you are going, but we hope you will find it interesting and useful.

For the More Curious: Class Names

In simple applications like RandomItems, you only need a few classes. As applications grow larger and more complex, the number of classes grows. At some point, you will run into a situation where you have two classes that could easily be named the same thing. This is bad news. If two classes have the same name, it is impossible for your program to figure out which one it should use. This is known as a namespace collision.

Other languages solve this problem by declaring classes inside a namespace. You can think of a namespace as a group to which classes belong. To use a class in these languages, you have to specify both the class name and the namespace.

Objective-C has no notion of namespaces. Instead, class names are prefixed with two or three letters to keep them distinct. For example, in this exercise, the class was named BNRItem instead of Item.

Stylish Objective-C programmers always prefix their classes. The prefix is typically related to the name of the application you are developing or the library that it belongs to. For example, if I were writing an application named “MovieViewer,” I would prefix all classes with MOV. Classes that you will use across multiple projects typically bear a prefix that is related to your name (CBK), your company’s name (BNR), or a portable library (a library for dealing with maps might use MAP).

Notice that Apple’s classes have prefixes, too. Apple’s classes are organized into frameworks, and each framework has its own prefix. For instance, the UILabel class belongs to the UIKit framework. The classes NSArray and NSString belong to the Foundation framework. (The NS stands for NeXTSTEP, the platform for which these classes were originally designed.)

For your classes, you should use three-letter prefixes. Two-letter prefixes are reserved by Apple for use in framework classes. Although nothing is stopping you from creating a class with a two-letter prefix, you should use three-letter prefixes to eliminate the possibility of namespace collisions with Apple’s present and future classes.

For the More Curious: #import and @import

When Objective-C was new, the system did not ship with many classes. Eventually, however, there were enough classes that it become necessary to organize them into frameworks. In your source code, you would typically #import the master header file for a framework:

#import <Foundation/Foundation.h>

And that file would #import all the headers in that framework, like this:

#import <Foundation/NSArray.h>

#import <Foundation/NSAutoreleasePool.h>

#import <Foundation/NSBundle.h>

#import <Foundation/NSByteOrder.h>

#import <Foundation/NSCalendar.h>

#import <Foundation/NSCharacterSet.h>

...

And then you would explicitly link that framework into your program at compile time.

This was easy to implement; it was using the existing C preprocessor to copy all these headers into the file that was about to be compiled.

This approach worked pretty well for about a decade. Then, as more classes were added to the frameworks and more frameworks went into each project, we noticed that the compiler was spending most of its time parsing and processing those same standard headers again and again. So, theprecompiled header file was added to every project. The first time you compiled your project, the headers listed in the that file would be compiled once and the result would be cached away. Having this pre-digested clump of headers made compiling all the other files much, much faster. The project you just created has the file RandomItems-Prefix.pch and it forces the build system to precompile the headers for the Foundation framework:

#ifdef __OBJC__

#import <Foundation/Foundation.h>

#endif

You still had to explicitly link that framework into your program at compile time.

That worked pretty well for another decade, but recently Apple realized that developers were not maintaining their .pch files effectively. So, they made the compiler smarter and the @import directive was introduced:

@import Foundation;

This tells the compiler, “Hey, I’m using the Foundation module. You figure out how to make that work.” The compiler is given a lot of freedom to optimize the preprocessing and caching of header files. (This also eliminates the need to explicitly link the module into the program – when the compiler sees the @import, it makes a note to link in the appropriate module.)

As we write this, only Apple can create modules that can be used with @import. To use classes and frameworks that you create, you will still need to use #import.

We are writing this book on Xcode 5.0, and #import still appears in the template projects and files, but we are certain that in the near future @import will be ubiquitous.