Subclassing an Objective-C Class

Classes exist in a hierarchy, and every class has exactly one superclass – except for the root class of the entire hierarchy: NSObject (Figure 2.8). A class inherits the behavior of its superclass, which means, at minimum, every class inherits the methods and instance variables defined in NSObject. As the top superclass, NSObject’s role is to implement the basic behavior of every object in Cocoa Touch. Two of the methods NSObject implements are alloc and description. (We sometimes say description is a method on NSObject and mean the same thing.)

Figure 2.8  Class hierarchy

Class hierarchy

A subclass can add methods and instance variables to extend the behavior of its superclass. For example, NSMutableArray extends NSArray’s ability to hold pointers to objects by adding the ability to dynamically add and remove objects.

A subclass can also override methods of its superclass. For example, NSString overrides the description method of NSObject. Sending the description message to an NSObject returns information about that instance. By default, description returns the object’s class and its address in memory, like this: <QuizAppDelegate: 0x4b222a0>.

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

In this exercise, you’re going to create a subclass of NSObject named Possession. An instance of the Possession class will represent an item that a person owns in the real world. Click FileNewNew File.... Select Cocoa Touch from the iOS section in the lefthand table. Then select Objective-C class from the upper panel and hit Next, as shown in Figure 2.9.

Figure 2.9  Creating a class

Creating a class

On the next panel, select NSObject as the superclass and click Next, as shown in Figure 2.10.

Figure 2.10  Choosing a superclass

Choosing a superclass

Name the class Possession (Figure 2.11). When creating a new class for a project, you want to save the files that describe it inside the project’s source directory on the filesystem. By default, the current project directory is already selected for you. You can also choose the group in the project navigator that these files will be added to. Because these groups are simply for organizing and because this project is very small, the group doesn’t matter, so just stick with the default. Make sure the checkbox is selected for the RandomPossessions target. This ensures that this class will be compiled when the project is built. Click Save.

Figure 2.11  Saving a new class

Saving a new class

Creating the Possession class generated two files: Possession.h and Possession.m. Locate those files in the project navigator. Possession.h is the header file (also called an 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. Possession.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.

Open Possession.h in the editor area by clicking on it in the project navigator. The file currently looks like this:

#​i​m​p​o​r​t​ ​<​U​I​K​i​t​/​U​I​K​i​t​.​h​>​

@​i​n​t​e​r​f​a​c​e​ ​P​o​s​s​e​s​s​i​o​n​ ​:​ ​N​S​O​b​j​e​c​t​
{​

}​
@​e​n​d​

Let’s break down this interface declaration to figure out what it means. First, note that the C language retains all of its keywords, and any additional keywords added by Objective-C are distinguishable by the @ prefix. To declare a class in Objective-C, you use the keyword @interface followed by the name of this new class. After a colon comes the name of the superclass. Possession’s superclass is NSObject. Objective-C only allows single inheritance, so you will only ever see the following pattern:

@​i​n​t​e​r​f​a​c​e​ ​C​l​a​s​s​N​a​m​e​ ​:​ ​S​u​p​e​r​c​l​a​s​s​N​a​m​e​

Next comes the space for declaring instance variables. Instance variables must be declared inside the curly brace block immediately following the class and superclass declaration. After the closing curly brace, you declare any methods that this class implements. Finally, the @end keyword finishes off the declaration for the new class.

Instance variables

So far, the Possession class doesn’t add anything to its superclass NSObject. What it needs are some possession-like instance variables. A possession, in our world, is going to have a name, a serial number, a value, and a date of creation. In Possession.h, add instance variables to the Possession class:

#​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​ ​P​o​s​s​e​s​s​i​o​n​ ​:​ ​N​S​O​b​j​e​c​t​
{​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​p​o​s​s​e​s​s​i​o​n​N​a​m​e​;​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​s​e​r​i​a​l​N​u​m​b​e​r​;​
 ​ ​ ​ ​i​n​t​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​;​
 ​ ​ ​ ​N​S​D​a​t​e​ ​*​d​a​t​e​C​r​e​a​t​e​d​;​
}​
@​e​n​d​

Now every instance of Possession has a spot for a simple integer. It also has spots to hold references to two NSString instances and one NSDate instance. (A reference is another word for pointer; the asterisk (*) denotes that the variable is a pointer.) Figure 2.12 shows an example of a Possession instance after its instance variables have been given values.

Figure 2.12  A Possession instance

A Possession instance

Notice that Figure 2.12 shows a total of four objects: the Possession, two NSStrings and the NSDate. Each of these objects is its own object and exists independently of the others. The Possession only has pointer to the three other objects. These pointers are the instance variables of Possession.

For example, every Possession has a pointer instance variable named possessionName. This Possession’s possessionName points to an NSString instance whose contents are Red Sofa. The Red Sofa string does not live inside the Possession, though. The Possession instance knows where the Red Sofa NSString lives in memory and stores its address as possessionName. One way to think of this relationship is the Possession calls this string its possessionName.

The story is different for the instance variable valueInDollars. This instance variable is not a pointer to another object; it is just an int. Non-pointer instance variables are stored inside the object itself. This is not an easy idea to understand at first. Throughout this book, we will make use of object diagrams like this one to drive home the difference between an object and a pointer to an object.

Accessor methods

Now that you 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, one object cannot access the instance variables of another object.

Accessor methods look like this:

/​/​ ​G​e​t​t​e​r​
-​ ​(​N​S​S​t​r​i​n​g​ ​*​)​p​o​s​s​e​s​s​i​o​n​N​a​m​e​
{​
 ​ ​ ​ ​/​/​ ​R​e​t​u​r​n​ ​a​ ​p​o​i​n​t​e​r​ ​t​o​ ​t​h​e​ ​o​b​j​e​c​t​ ​t​h​i​s​ ​P​o​s​s​e​s​s​i​o​n​ ​c​a​l​l​s​ ​i​t​s​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​
 ​ ​ ​ ​r​e​t​u​r​n​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​;​
}​

/​/​ ​S​e​t​t​e​r​
-​ ​(​v​o​i​d​)​s​e​t​P​o​s​s​e​s​s​i​o​n​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​n​e​w​P​o​s​s​e​s​s​i​o​n​N​a​m​e​
{​
 ​ ​ ​ ​/​/​ ​C​h​a​n​g​e​ ​t​h​e​ ​i​n​s​t​a​n​c​e​ ​v​a​r​i​a​b​l​e​ ​t​o​ ​p​o​i​n​t​ ​a​t​ ​a​n​o​t​h​e​r​ ​s​t​r​i​n​g​,​
 ​ ​ ​ ​/​/​ ​t​h​i​s​ ​P​o​s​s​e​s​s​i​o​n​ ​w​i​l​l​ ​n​o​w​ ​c​a​l​l​ ​t​h​i​s​ ​n​e​w​ ​s​t​r​i​n​g​ ​i​t​s​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​
 ​ ​ ​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​ ​=​ ​n​e​w​P​o​s​s​e​s​s​i​o​n​N​a​m​e​;​
}​

Then, if you wanted to access a Possession’s possessionName, you would send it one of the following messages:

/​/​ ​C​r​e​a​t​e​ ​a​ ​n​e​w​ ​P​o​s​s​e​s​s​i​o​n​ ​i​n​s​t​a​n​c​e​
P​o​s​s​e​s​s​i​o​n​ ​*​p​ ​=​ ​[​[​P​o​s​s​e​s​s​i​o​n​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​

/​/​ ​S​e​t​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​ ​t​o​ ​a​ ​n​e​w​ ​N​S​S​t​r​i​n​g​
[​p​ ​s​e​t​P​o​s​s​e​s​s​i​o​n​N​a​m​e​:​@​"​R​e​d​ ​S​o​f​a​"​]​;​

/​/​ ​G​e​t​ ​t​h​e​ ​p​o​i​n​t​e​r​ ​o​f​ ​t​h​e​ ​P​o​s​s​e​s​s​i​o​n​'​s​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​
N​S​S​t​r​i​n​g​ ​*​s​t​r​ ​=​ ​[​p​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​]​;​

/​/​ ​P​r​i​n​t​ ​t​h​a​t​ ​o​b​j​e​c​t​
N​S​L​o​g​(​@​"​%​@​"​,​ ​s​t​r​)​;​ ​/​/​ ​T​h​i​s​ ​w​o​u​l​d​ ​p​r​i​n​t​ ​"​R​e​d​ ​S​o​f​a​"​

In Objective-C, the name of a setter method is set plus the name of the instance variable it is changing – in this case, setPossessionName:. In other languages, the name of the getter method would likely be getPossessionName. 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.

In Possession.h, declare accessor methods for the instance variables of Possession. You will need getters and setters for valueInDollars, possessionName, and serialNumber. For dateCreated, you only need a getter method.

#​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​ ​P​o​s​s​e​s​s​i​o​n​ ​:​ ​N​S​O​b​j​e​c​t​
{​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​p​o​s​s​e​s​s​i​o​n​N​a​m​e​;​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​s​e​r​i​a​l​N​u​m​b​e​r​;​
 ​ ​ ​ ​i​n​t​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​;​
 ​ ​ ​ ​N​S​D​a​t​e​ ​*​d​a​t​e​C​r​e​a​t​e​d​;​
}​
-​ ​(​v​o​i​d​)​s​e​t​P​o​s​s​e​s​s​i​o​n​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​t​r​;​
-​ ​(​N​S​S​t​r​i​n​g​ ​*​)​p​o​s​s​e​s​s​i​o​n​N​a​m​e​;​

-​ ​(​v​o​i​d​)​s​e​t​S​e​r​i​a​l​N​u​m​b​e​r​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​t​r​;​
-​ ​(​N​S​S​t​r​i​n​g​ ​*​)​s​e​r​i​a​l​N​u​m​b​e​r​;​

-​ ​(​v​o​i​d​)​s​e​t​V​a​l​u​e​I​n​D​o​l​l​a​r​s​:​(​i​n​t​)​i​;​
-​ ​(​i​n​t​)​v​a​l​u​e​I​n​D​o​l​l​a​r​s​;​

-​ ​(​N​S​D​a​t​e​ ​*​)​d​a​t​e​C​r​e​a​t​e​d​;​
@​e​n​d​

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

Now that these accessors have been declared, they need to be defined in the implementation file. Open Possession.m in the editor area by selecting it in the project navigator.

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

After the import statements is an 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.

We’re going to skip memory management until the next chapter, so the accessor methods for Possession are very simple: setter methods assign the appropriate instance variable to point at the incoming object, and getter methods return a pointer to the object the instance variable points at. (For valueInDollars, we’re just assigning the passed-in value to the instance variable for the setter and returning the value in the getter.) Edit Possession.m:

#​i​m​p​o​r​t​ ​"​P​o​s​s​e​s​s​i​o​n​.​h​"​

@​i​m​p​l​e​m​e​n​t​a​t​i​o​n​ ​P​o​s​s​e​s​s​i​o​n​

-​ ​(​v​o​i​d​)​s​e​t​P​o​s​s​e​s​s​i​o​n​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​t​r​
{​
 ​ ​ ​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​ ​=​ ​s​t​r​;​
}​
-​ ​(​N​S​S​t​r​i​n​g​ ​*​)​p​o​s​s​e​s​s​i​o​n​N​a​m​e​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​;​
}​

-​ ​(​v​o​i​d​)​s​e​t​S​e​r​i​a​l​N​u​m​b​e​r​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​t​r​
{​
 ​ ​ ​ ​s​e​r​i​a​l​N​u​m​b​e​r​ ​=​ ​s​t​r​;​
}​

-​ ​(​N​S​S​t​r​i​n​g​ ​*​)​s​e​r​i​a​l​N​u​m​b​e​r​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​s​e​r​i​a​l​N​u​m​b​e​r​;​
}​

-​ ​(​v​o​i​d​)​s​e​t​V​a​l​u​e​I​n​D​o​l​l​a​r​s​:​(​i​n​t​)​i​
{​
 ​ ​ ​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​ ​=​ ​i​;​
}​

-​ ​(​i​n​t​)​v​a​l​u​e​I​n​D​o​l​l​a​r​s​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​;​
}​

-​ ​(​N​S​D​a​t​e​ ​*​)​d​a​t​e​C​r​e​a​t​e​d​
{​
 ​ ​ ​ ​r​e​t​u​r​n​ ​d​a​t​e​C​r​e​a​t​e​d​;​
}​

@​e​n​d​

Build your application (select ProductBuild or use the shortcut Command-B) to ensure that there are no compiler errors or warnings.

Now that your accessors have been declared and defined, you can send messages to Possession instances to get and set their instance variables. Let’s test this out. In main.m, import the header file for Possession and create a new Possession instance. After it is created, log its instance variables to the console.

#​i​m​p​o​r​t​ ​"​P​o​s​s​e​s​s​i​o​n​.​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​[​]​)​
{​
 ​ ​ ​ ​N​S​A​u​t​o​r​e​l​e​a​s​e​P​o​o​l​ ​*​p​o​o​l​ ​=​ ​[​[​N​S​A​u​t​o​r​e​l​e​a​s​e​P​o​o​l​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​

 ​ ​ ​ ​N​S​M​u​t​a​b​l​e​A​r​r​a​y​ ​*​i​t​e​m​s​ ​=​ ​[​[​N​S​M​u​t​a​b​l​e​A​r​r​a​y​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​
 ​ ​ ​ ​[​i​t​e​m​s​ ​a​d​d​O​b​j​e​c​t​:​@​"​O​n​e​"​]​;​
 ​ ​ ​ ​[​i​t​e​m​s​ ​a​d​d​O​b​j​e​c​t​:​@​"​T​w​o​"​]​;​
 ​ ​ ​ ​[​i​t​e​m​s​ ​a​d​d​O​b​j​e​c​t​:​@​"​T​h​r​e​e​"​]​;​
 ​ ​ ​ ​[​i​t​e​m​s​ ​i​n​s​e​r​t​O​b​j​e​c​t​:​@​"​Z​e​r​o​"​ ​a​t​I​n​d​e​x​:​0​]​;​

 ​ ​ ​ ​f​o​r​(​i​n​t​ ​i​ ​=​ ​0​;​ ​i​ ​<​ ​[​i​t​e​m​s​ ​c​o​u​n​t​]​;​ ​i​+​+​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​%​@​"​,​ ​[​i​t​e​m​s​ ​o​b​j​e​c​t​A​t​I​n​d​e​x​:​i​]​)​;​
 ​ ​ ​ ​}​

 ​ ​ ​ ​P​o​s​s​e​s​s​i​o​n​ ​*​p​ ​=​ ​[​[​P​o​s​s​e​s​s​i​o​n​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​
 ​ ​ ​ ​N​S​L​o​g​(​@​"​%​@​ ​%​@​ ​%​@​ ​%​d​"​,​ ​[​p​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​]​,​ ​[​p​ ​d​a​t​e​C​r​e​a​t​e​d​]​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​p​ ​s​e​r​i​a​l​N​u​m​b​e​r​]​,​ ​[​p​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​]​)​;​

 ​ ​ ​ ​[​i​t​e​m​s​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​i​t​e​m​s​ ​=​ ​n​i​l​;​

 ​ ​ ​ ​[​p​o​o​l​ ​d​r​a​i​n​]​;​
 ​ ​ ​ ​r​e​t​u​r​n​ ​0​;​
}​

Build and run the application. Check the console by selecting the most recent entry in the log navigator. You should see the previous console output followed by a line that has three (null) strings and a 0. (When an object is created, all of its instance variables are set to 0. For primitives like int, the value is 0; for pointers to objects, that pointer points to nil.)

To give this Possession some substance, you need to create new objects and pass them as arguments to the setter methods for this instance. In main.m, type in the following code:

/​/​ ​N​o​t​i​c​e​ ​w​e​ ​o​m​i​t​t​e​d​ ​s​o​m​e​ ​o​f​ ​t​h​e​ ​s​u​r​r​o​u​n​d​i​n​g​ ​c​o​d​e​.​ ​T​h​e​ ​b​o​l​d​ ​c​o​d​e​ ​i​s​ ​t​h​e​ ​c​o​d​e​ ​t​o​ ​a​d​d​,​
/​/​ ​t​h​e​ ​n​o​n​-​b​o​l​d​ ​c​o​d​e​ ​i​s​ ​e​x​i​s​t​i​n​g​ ​c​o​d​e​ ​t​h​a​t​ ​s​h​o​w​s​ ​y​o​u​ ​w​h​e​r​e​ ​t​o​ ​t​y​p​e​ ​i​n​ ​t​h​e​ ​n​e​w​ ​s​t​u​f​f​.​

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

/​/​ ​T​h​i​s​ ​c​r​e​a​t​e​s​ ​a​ ​n​e​w​ ​N​S​S​t​r​i​n​g​,​ ​"​R​e​d​ ​S​o​f​a​"​,​ ​a​n​d​ ​g​i​v​e​s​ ​i​t​ ​t​o​ ​t​h​e​ ​P​o​s​s​e​s​s​i​o​n​
[​p​ ​s​e​t​P​o​s​s​e​s​s​i​o​n​N​a​m​e​:​@​"​R​e​d​ ​S​o​f​a​"​]​;​

/​/​ ​T​h​i​s​ ​c​r​e​a​t​e​s​ ​a​ ​n​e​w​ ​N​S​S​t​r​i​n​g​,​ ​"​A​1​B​2​C​"​,​ ​a​n​d​ ​g​i​v​e​s​ ​i​t​ ​t​o​ ​t​h​e​ ​P​o​s​s​e​s​s​i​o​n​
[​p​ ​s​e​t​S​e​r​i​a​l​N​u​m​b​e​r​:​@​"​A​1​B​2​C​"​]​;​

/​/​ ​W​e​ ​s​e​n​d​ ​t​h​e​ ​v​a​l​u​e​ ​1​0​0​ ​t​o​ ​b​e​ ​u​s​e​d​ ​a​s​ ​t​h​e​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​ ​o​f​ ​t​h​i​s​ ​P​o​s​s​e​s​s​i​o​n​
[​p​ ​s​e​t​V​a​l​u​e​I​n​D​o​l​l​a​r​s​:​1​0​0​]​;​

N​S​L​o​g​(​@​"​%​@​ ​%​@​ ​%​@​ ​%​d​"​,​ ​[​p​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​]​,​ ​[​p​ ​d​a​t​e​C​r​e​a​t​e​d​]​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​p​ ​s​e​r​i​a​l​N​u​m​b​e​r​]​,​ ​[​p​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​]​)​;​

Build and run the application. Now you should see values for everything but the dateCreated, which we’ll take care of shortly.

Instance methods

Not all instance methods are accessors. You will regularly find yourself wanting to send messages to instances that perform other tasks. One such message is description. You can implement this method for Possession to return a string that describes a Possession instance. Because Possession is a subclass of NSObject (the class that originally declares the description method), when you re-implement this method in the Possession class, 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 Possession.m, override the description method. This new code can go anywhere between @implementation and @end, as long as it’s not inside the curly brackets of an existing method.

-​ ​(​N​S​S​t​r​i​n​g​ ​*​)​d​e​s​c​r​i​p​t​i​o​n​
{​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​d​e​s​c​r​i​p​t​i​o​n​S​t​r​i​n​g​ ​=​
 ​ ​ ​ ​ ​ ​ ​ ​[​[​N​S​S​t​r​i​n​g​ ​a​l​l​o​c​]​ ​i​n​i​t​W​i​t​h​F​o​r​m​a​t​:​@​"​%​@​ ​(​%​@​)​:​ ​W​o​r​t​h​ ​$​%​d​,​ ​r​e​c​o​r​d​e​d​ ​o​n​ ​%​@​"​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​p​o​s​s​e​s​s​i​o​n​N​a​m​e​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​s​e​r​i​a​l​N​u​m​b​e​r​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​d​a​t​e​C​r​e​a​t​e​d​]​;​

 ​ ​ ​ ​r​e​t​u​r​n​ ​d​e​s​c​r​i​p​t​i​o​n​S​t​r​i​n​g​;​

}​

Now whenever you send the message description to an instance of Possession, it will return an NSString that describes that instance. (To those of you familiar with Objective-C and managing memory, don’t panic – you will fix the problem with this code in the next chapter.) In main.m, substitute this new method into the NSLog that prints out the instance variables of the Possession.

[​p​ ​s​e​t​V​a​l​u​e​I​n​D​o​l​l​a​r​s​:​1​0​0​]​;​

/​/​ ​R​e​m​e​m​b​e​r​,​ ​a​n​ ​N​S​L​o​g​ ​w​i​t​h​ ​%​@​ ​a​s​ ​t​h​e​ ​t​o​k​e​n​ ​w​i​l​l​ ​p​r​i​n​t​ ​t​h​e​
/​/​ ​d​e​s​c​r​i​p​t​i​o​n​ ​o​f​ ​t​h​e​ ​c​o​r​r​e​s​p​o​n​d​i​n​g​ ​a​r​g​u​m​e​n​t​
N​S​L​o​g​(​@​"​%​@​"​,​ ​p​)​;​

Build and run the application and check your results in the log navigator. You should see a log statement that looks like this:

R​e​d​ ​S​o​f​a​ ​(​A​1​B​2​C​)​:​ ​W​o​r​t​h​ ​$​1​0​0​,​ ​r​e​c​o​r​d​e​d​ ​o​n​ ​(​n​u​l​l​)​

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. A good method to begin with is an object’s initializer.

Initializers

At the beginning of this chapter, we discussed how an instance is created: its class is sent the message alloc, which creates an instance of that class and returns a pointer to it, and then that instance is sent the message init, which gives its instance variables initial values. As you start to write more complicated classes, you will want to create initialization methods like init that take arguments that the object can use to initialize itself. For example, the Possession class would be much cleaner if we could pass one or more of its variables as part of the initialization process.

To cover the different possible initialization scenarios, many classes have more than one initialization method, or initializer. Each initializer begins with the word init. Naming initializers this way doesn’t 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. (Seriously. Disregarding naming conventions in Objective-C results in problems that are worse than most beginners would imagine.)

For each class, regardless of how many initialization methods there are, one method is chosen as the designated initializer. For NSObject, there is only one initializer, init, so it is the designated initializer. The designated initializer makes sure that every instance variable of an object is valid. (Valid has different meanings, but in this context it 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 Possession class has four instance variables, but only three are writeable. Therefore, Possession’s designated initializer needs to accept three arguments. In Possession.h, declare the designated initializer:

 ​ ​ ​ ​N​S​D​a​t​e​ ​*​d​a​t​e​C​r​e​a​t​e​d​;​
}​

-​ ​(​i​d​)​i​n​i​t​W​i​t​h​P​o​s​s​e​s​s​i​o​n​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​n​a​m​e​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​:​(​i​n​t​)​v​a​l​u​e​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​s​e​r​i​a​l​N​u​m​b​e​r​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​N​u​m​b​e​r​;​

-​ ​(​v​o​i​d​)​s​e​t​P​o​s​s​e​s​s​i​o​n​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​t​r​;​

This method’s name, or selector, is initWithPossessionName:​valueInDollars:​serialNumber:. This selector has three labels (initWithPossessionName:, valueInDollars:, and serialNumber:), which tells you the method accepts three arguments.

These arguments each have a type and a parameter name. In the declaration, the type follows the label in parentheses. The parameter name then follows the type. So the label initWithPossessionName: is expecting a pointer to an instance of type NSString. Within the body of that method, you can use name to reference the NSString object pointed to.

id

Take another look at the initializer’s declaration. Its return type is id, which is defined as a pointer to any object. (This is a lot like void * in C and is pronounced eye-dee.) init methods are always declared to return id.

Why not make the return type Possession * – a pointer to a Possession? After all, that is the type of object that is returned from this method. A problem will arise, however, if Possession is ever subclassed. The subclass would inherit all of the methods from Possession, including this initializer and its return type. An instance of the subclass could then be sent this initializer message, but what would be returned? Not a Possession, but an instance of the subclass. You might think, No problem. Override the initializer in the subclass to change the return type. But remember – 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 any object, we never have to worry what happens with a subclass.

isa

As programmers, we always know the type of the object that is returned from an initializer. (How do we know this? It is an instance of the class we sent alloc to.) Not only do we know the type of the object, the object itself knows its type.

Every object has an instance variable called isa. When an instance is created by sending alloc to a class, that class sets the isa instance variable of the returned object to point back at the class that created it (Figure 2.13). We call it the isa pointer because an object is a instance of that class.

Figure 2.13  The isa pointer

The isa pointer

The isa pointer is where Objective-C holds much of its power. At runtime, when a message is sent to an object, that object goes to the class named in its isa pointer and says, I was sent this message. Run the code for the matching method. This is different than most compiled languages, where the method to be executed is determined at compile time.

Implementing the designated initializer

Now that you have declared the designated initializer in Possession.h, you need to implement it. Open Possession.m. Recall that the definitions for methods go within the implementation block in the implementation file, and add the designated initializer there.

@​i​m​p​l​e​m​e​n​t​a​t​i​o​n​ ​P​o​s​s​e​s​s​i​o​n​

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

 ​ ​ ​ ​/​/​ ​G​i​v​e​ ​t​h​e​ ​i​n​s​t​a​n​c​e​ ​v​a​r​i​a​b​l​e​s​ ​i​n​i​t​i​a​l​ ​v​a​l​u​e​s​
 ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​P​o​s​s​e​s​s​i​o​n​N​a​m​e​:​n​a​m​e​]​;​
 ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​S​e​r​i​a​l​N​u​m​b​e​r​:​s​N​u​m​b​e​r​]​;​
 ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​V​a​l​u​e​I​n​D​o​l​l​a​r​s​:​v​a​l​u​e​]​;​
 ​ ​ ​ ​d​a​t​e​C​r​e​a​t​e​d​ ​=​ ​[​[​N​S​D​a​t​e​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​

 ​ ​ ​ ​/​/​ ​R​e​t​u​r​n​ ​t​h​e​ ​a​d​d​r​e​s​s​ ​o​f​ ​t​h​e​ ​n​e​w​l​y​ ​i​n​i​t​i​a​l​i​z​e​d​ ​o​b​j​e​c​t​
 ​ ​ ​ ​r​e​t​u​r​n​ ​s​e​l​f​;​
}​

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’s going on in an initializer, you will 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:

-​ ​(​v​o​i​d​)​c​h​i​c​k​e​n​D​a​n​c​e​
{​
 ​ ​ ​ ​[​s​e​l​f​ ​p​r​e​t​e​n​d​H​a​n​d​s​A​r​e​B​e​a​k​s​]​;​
 ​ ​ ​ ​[​s​e​l​f​ ​f​l​a​p​W​i​n​g​s​]​;​
 ​ ​ ​ ​[​s​e​l​f​ ​s​h​a​k​e​T​a​i​l​F​e​a​t​h​e​r​s​]​;​
}​

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

r​e​t​u​r​n​ ​s​e​l​f​;​

super

Often when you are overriding a method in a subclass, you want it to add something to what the existing method of the superclass already does. To make this easier, there is a compiler directive in Objective-C called super:

-​ ​(​v​o​i​d​)​s​o​m​e​M​e​t​h​o​d​
{​
 ​ ​ ​ ​[​s​e​l​f​ ​d​o​M​o​r​e​S​t​u​f​f​]​;​
 ​ ​ ​ ​[​s​u​p​e​r​ ​s​o​m​e​M​e​t​h​o​d​]​;​
}​

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 demanding that the search for the method begin at the superclass.

In the case of Possession’s designated initializer, we send the init message to super. This calls NSObject’s implementation of init. 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. In Possession.m, edit your designated initializer to confirm the initialization of the superclass.

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

 ​ ​ ​ ​/​/​ ​D​i​d​ ​t​h​e​ ​s​u​p​e​r​c​l​a​s​s​'​s​ ​d​e​s​i​g​n​a​t​e​d​ ​i​n​i​t​i​a​l​i​z​e​r​ ​s​u​c​c​e​e​d​?​
 ​ ​ ​ ​i​f​ ​(​s​e​l​f​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​G​i​v​e​ ​t​h​e​ ​i​n​s​t​a​n​c​e​ ​v​a​r​i​a​b​l​e​s​ ​i​n​i​t​i​a​l​ ​v​a​l​u​e​s​
 ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​P​o​s​s​e​s​s​i​o​n​N​a​m​e​:​n​a​m​e​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​S​e​r​i​a​l​N​u​m​b​e​r​:​s​N​u​m​b​e​r​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​s​e​t​V​a​l​u​e​I​n​D​o​l​l​a​r​s​:​v​a​l​u​e​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​d​a​t​e​C​r​e​a​t​e​d​ ​=​ ​[​[​N​S​D​a​t​e​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​
 ​ ​ ​ ​}​

 ​ ​ ​ ​/​/​ ​R​e​t​u​r​n​ ​t​h​e​ ​a​d​d​r​e​s​s​ ​o​f​ ​t​h​e​ ​n​e​w​l​y​ ​i​n​i​t​i​a​l​i​z​e​d​ ​o​b​j​e​c​t​
 ​ ​ ​ ​r​e​t​u​r​n​ ​s​e​l​f​;​
}​

Other initializers and the initializer chain

A class can have more than one initializer. For example, Possession could have an initializer that takes an NSString for the possessionName, but not the serialNumber or valueInDollars. Instead of replicating all of the code in the designated initializer, this other initializer would simply call the designated initializer. It would pass the information it was given for the possessionName and pass default values for the other two arguments.

Possession’s designated initializer is initWithPossessionName:​valueInDollars:​serialNumber:. However, it has another initializer, init, that it inherits it from its superclass NSObject. If init is sent to an instance of Possession, none of the stuff you put in the designated initializer will be called. Therefore, you must link Possession’s implementation of init to its designated initializer. In Possession.m, override the init method to call the designated initializer with default values.

-​ ​(​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​o​s​s​e​s​s​i​o​n​N​a​m​e​:​@​"​P​o​s​s​e​s​s​i​o​n​"​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​:​0​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​s​e​r​i​a​l​N​u​m​b​e​r​:​@​"​"​]​;​
}​

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. This relationship is shown in Figure 2.14; the designated initializers are white, and the additional initializer is gray.

Figure 2.14  Initializer chain

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, you must override the superclass’ designated initializer to call the new 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 Possession class, it would be nice if you could create a random possession so that you could easily test your class without having to think up a bunch of clever names. Declare a class method in Possession.h that will create a random possession.

@​i​n​t​e​r​f​a​c​e​ ​P​o​s​s​e​s​s​i​o​n​ ​:​ ​N​S​O​b​j​e​c​t​
{​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​p​o​s​s​e​s​s​i​o​n​N​a​m​e​;​
 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​s​e​r​i​a​l​N​u​m​b​e​r​;​
 ​ ​ ​ ​i​n​t​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​;​
 ​ ​ ​ ​N​S​D​a​t​e​ ​*​d​a​t​e​C​r​e​a​t​e​d​;​
}​

+​ ​(​i​d​)​r​a​n​d​o​m​P​o​s​s​e​s​s​i​o​n​;​

-​ ​(​i​d​)​i​n​i​t​W​i​t​h​P​o​s​s​e​s​s​i​o​n​N​a​m​e​:​(​N​S​S​t​r​i​n​g​ ​*​)​n​a​m​e​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​:​(​i​n​t​)​v​a​l​u​e​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​s​e​r​i​a​l​N​u​m​b​e​r​:​(​N​S​S​t​r​i​n​g​ ​*​)​s​N​u​m​b​e​r​;​

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.

Class methods that return an instance of their type create an instance (with alloc and init), configure it, and then return it. In Possession.m, implement randomPossession to create, configure, and return a Possession instance (make sure this method is between the @implementation and @end):

+​ ​(​i​d​)​r​a​n​d​o​m​P​o​s​s​e​s​s​i​o​n​
{​
 ​ ​ ​ ​/​/​ ​C​r​e​a​t​e​ ​a​n​ ​a​r​r​a​y​ ​o​f​ ​t​h​r​e​e​ ​a​d​j​e​c​t​i​v​e​s​
 ​ ​ ​ ​N​S​A​r​r​a​y​ ​*​r​a​n​d​o​m​A​d​j​e​c​t​i​v​e​L​i​s​t​ ​=​ ​[​N​S​A​r​r​a​y​ ​a​r​r​a​y​W​i​t​h​O​b​j​e​c​t​s​:​@​"​F​l​u​f​f​y​"​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​@​"​R​u​s​t​y​"​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​@​"​S​h​i​n​y​"​,​ ​n​i​l​]​;​

 ​ ​ ​ ​/​/​ ​C​r​e​a​t​e​ ​a​n​ ​a​r​r​a​y​ ​o​f​ ​t​h​r​e​e​ ​n​o​u​n​s​
 ​ ​ ​ ​N​S​A​r​r​a​y​ ​*​r​a​n​d​o​m​N​o​u​n​L​i​s​t​ ​=​ ​[​N​S​A​r​r​a​y​ ​a​r​r​a​y​W​i​t​h​O​b​j​e​c​t​s​:​@​"​B​e​a​r​"​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​@​"​S​p​o​r​k​"​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​@​"​M​a​c​"​,​ ​n​i​l​]​;​

 ​ ​ ​ ​/​/​ ​G​e​t​ ​t​h​e​ ​i​n​d​e​x​ ​o​f​ ​a​ ​r​a​n​d​o​m​ ​a​d​j​e​c​t​i​v​e​/​n​o​u​n​ ​f​r​o​m​ ​t​h​e​ ​l​i​s​t​s​
 ​ ​ ​ ​/​/​ ​N​o​t​e​:​ ​T​h​e​ ​%​ ​o​p​e​r​a​t​o​r​,​ ​c​a​l​l​e​d​ ​t​h​e​ ​m​o​d​u​l​o​ ​o​p​e​r​a​t​o​r​,​ ​g​i​v​e​s​
 ​ ​ ​ ​/​/​ ​y​o​u​ ​t​h​e​ ​r​e​m​a​i​n​d​e​r​.​ ​S​o​ ​a​d​j​e​c​t​i​v​e​I​n​d​e​x​ ​i​s​ ​a​ ​r​a​n​d​o​m​ ​n​u​m​b​e​r​
 ​ ​ ​ ​/​/​ ​f​r​o​m​ ​0​ ​t​o​ ​2​ ​i​n​c​l​u​s​i​v​e​.​
 ​ ​ ​ ​i​n​t​ ​a​d​j​e​c​t​i​v​e​I​n​d​e​x​ ​=​ ​r​a​n​d​(​)​ ​%​ ​[​r​a​n​d​o​m​A​d​j​e​c​t​i​v​e​L​i​s​t​ ​c​o​u​n​t​]​;​
 ​ ​ ​ ​i​n​t​ ​n​o​u​n​I​n​d​e​x​ ​=​ ​r​a​n​d​(​)​ ​%​ ​[​r​a​n​d​o​m​N​o​u​n​L​i​s​t​ ​c​o​u​n​t​]​;​

 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​r​a​n​d​o​m​N​a​m​e​ ​=​ ​[​N​S​S​t​r​i​n​g​ ​s​t​r​i​n​g​W​i​t​h​F​o​r​m​a​t​:​@​"​%​@​ ​%​@​"​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​r​a​n​d​o​m​A​d​j​e​c​t​i​v​e​L​i​s​t​ ​o​b​j​e​c​t​A​t​I​n​d​e​x​:​a​d​j​e​c​t​i​v​e​I​n​d​e​x​]​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​r​a​n​d​o​m​N​o​u​n​L​i​s​t​ ​o​b​j​e​c​t​A​t​I​n​d​e​x​:​n​o​u​n​I​n​d​e​x​]​]​;​

 ​ ​ ​ ​i​n​t​ ​r​a​n​d​o​m​V​a​l​u​e​ ​=​ ​r​a​n​d​(​)​ ​%​ ​1​0​0​;​

 ​ ​ ​ ​N​S​S​t​r​i​n​g​ ​*​r​a​n​d​o​m​S​e​r​i​a​l​N​u​m​b​e​r​ ​=​ ​[​N​S​S​t​r​i​n​g​ ​s​t​r​i​n​g​W​i​t​h​F​o​r​m​a​t​:​@​"​%​c​%​c​%​c​%​c​%​c​"​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​'​0​'​ ​+​ ​r​a​n​d​(​)​ ​%​ ​1​0​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​'​A​'​ ​+​ ​r​a​n​d​(​)​ ​%​ ​2​6​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​'​0​'​ ​+​ ​r​a​n​d​(​)​ ​%​ ​1​0​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​'​A​'​ ​+​ ​r​a​n​d​(​)​ ​%​ ​2​6​,​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​'​0​'​ ​+​ ​r​a​n​d​(​)​ ​%​ ​1​0​]​;​

 ​ ​ ​ ​/​/​ ​O​n​c​e​ ​a​g​a​i​n​,​ ​i​g​n​o​r​e​ ​t​h​e​ ​m​e​m​o​r​y​ ​p​r​o​b​l​e​m​s​ ​w​i​t​h​ ​t​h​i​s​ ​m​e​t​h​o​d​
 ​ ​ ​ ​P​o​s​s​e​s​s​i​o​n​ ​*​n​e​w​P​o​s​s​e​s​s​i​o​n​ ​=​
 ​ ​ ​ ​ ​ ​ ​ ​[​[​s​e​l​f​ ​a​l​l​o​c​]​ ​i​n​i​t​W​i​t​h​P​o​s​s​e​s​s​i​o​n​N​a​m​e​:​r​a​n​d​o​m​N​a​m​e​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​v​a​l​u​e​I​n​D​o​l​l​a​r​s​:​r​a​n​d​o​m​V​a​l​u​e​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​s​e​r​i​a​l​N​u​m​b​e​r​:​r​a​n​d​o​m​S​e​r​i​a​l​N​u​m​b​e​r​]​;​
 ​ ​ ​ ​r​e​t​u​r​n​ ​n​e​w​P​o​s​s​e​s​s​i​o​n​;​
}​

This method creates two arrays using the method arrayWithObjects:. arrayWithObjects: 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 randomPossession creates a string from a random adjective and noun, another string from random numbers and letters, and a random integer value. It then creates an instance of Possession and sends it the designated initializer with these randomly-created objects and int as parameters.

In this method, you also use 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 randomPossession) are called convenience methods.

Notice the use of self in randomPossession. Because randomPossession is a class method, self refers to the Possession 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 Possession, you can send that subclass the message randomPossession. Using self (instead of Possession) will allocate an instance of the class that was sent the message and set the instance’s isa pointer to that class as well.

Testing your subclass

Open main.m. Currently, in the main function, you are adding NSString instances to an NSMutableArray instance and then printing them to the console. Now you will add Possession instances to the array and log them instead. Delete the code that previously created a single Possession and change your main function to look just like this:

#​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​ ​"​P​o​s​s​e​s​s​i​o​n​.​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​[​]​)​
{​
 ​ ​ ​ ​N​S​A​u​t​o​r​e​l​e​a​s​e​P​o​o​l​ ​*​ ​p​o​o​l​ ​=​ ​[​[​N​S​A​u​t​o​r​e​l​e​a​s​e​P​o​o​l​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​

 ​ ​ ​ ​N​S​M​u​t​a​b​l​e​A​r​r​a​y​ ​*​i​t​e​m​s​ ​=​ ​[​[​N​S​M​u​t​a​b​l​e​A​r​r​a​y​ ​a​l​l​o​c​]​ ​i​n​i​t​]​;​

 ​ ​ ​ ​f​o​r​ ​(​i​n​t​ ​i​ ​=​ ​0​;​ ​i​ ​<​ ​1​0​;​ ​i​+​+​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​P​o​s​s​e​s​s​i​o​n​ ​*​p​ ​=​ ​[​P​o​s​s​e​s​s​i​o​n​ ​r​a​n​d​o​m​P​o​s​s​e​s​s​i​o​n​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​[​i​t​e​m​s​ ​a​d​d​O​b​j​e​c​t​:​p​]​;​
 ​ ​ ​ ​}​


 ​ ​ ​ ​f​o​r​ ​(​i​n​t​ ​i​ ​=​ ​0​;​ ​i​ ​<​ ​[​i​t​e​m​s​ ​c​o​u​n​t​]​;​ ​i​+​+​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​N​S​L​o​g​(​@​"​%​@​"​,​ ​[​i​t​e​m​s​ ​o​b​j​e​c​t​A​t​I​n​d​e​x​:​i​]​)​;​
 ​ ​ ​ ​}​

 ​ ​ ​ ​[​i​t​e​m​s​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​i​t​e​m​s​ ​=​ ​n​i​l​;​

 ​ ​ ​ ​[​p​o​o​l​ ​d​r​a​i​n​]​;​
 ​ ​ ​ ​r​e​t​u​r​n​ ​0​;​
}​

Build and run your application and then check the output in the log navigator. All you did was replace what objects you added to the array, and the code runs perfectly fine with a different output (Figure 2.15). Creating this class was a success.

Figure 2.15  Application result

Application result

Check out the #import statements at the top of main.m. Why did you have to import the class header Possession.h when you didn’t you have to import, say, NSMutableArray.h? NSMutableArray comes from the Foundation framework, so it is included when you import Foundation/Foundation.h. On the other hand, your class exists in its own file, so you have to explicitly import it into main.m. Otherwise, the compiler won’t know it exists and will complain loudly.

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

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