The program Section
The program section contains the code to solve your particular problem, which can be spread out across many files, if necessary. Somewhere you must have a routine called main, as we've previously noted. That's where your program always begins execution. Here's the program section from Program 3.2:
//---- program section ---- int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Fraction *myFraction; // Create an instance of a Fraction and initialize it myFraction = [Fraction alloc]; myFraction = [myFraction init]; // Set fraction to 1/3 [myFraction setNumerator: 1]; [myFraction setDenominator: 3]; // Display the fraction using the print method NSLog (@"The value of myFraction is:"); [myFraction print]; [myFraction release]; [pool drain]; return 0; }
Inside main, you define a variable called myFraction with the following line:
Fraction *myFraction;
This line says that myFraction is an object of type Fraction; that is, myFraction is used to store values from your new Fraction class. The asterisk that precedes the variable name is described in more detail below.
Now that you have an object to store a Fraction, you need to create one, just as you ask the factory to build you a new car. This is done with the following line:
myFraction = [Fraction alloc];
alloc is short for allocate. You want to allocate memory storage space for a new fraction. This expression sends a message to your newly created Fraction class:
[Fraction alloc]
You are asking the Fraction class to apply the alloc method, but you never defined an alloc method, so where did it come from? The method was inherited from a parent class. Chapter 8, "Inheritance" deals with this topic in detail.
When you send the alloc message to a class, you get back a new instance of that class. In Program 3.2, the returned value is stored inside your variable myFraction. The alloc method is guaranteed to zero out all of an object's instance variables. However, that doesn't mean that the object has been properly initialized for use. You need to initialize an object after you allocate it.
This is done with the next statement in Program 3.2, which reads as follows:
myFraction = [myFraction init];
Again, you are using a method here that you didn't write yourself. The init method initializes the instance of a class. Note that you are sending the init message to myFraction. That is, you want to initialize a specific Fraction object here, so you don't send it to the class—you send it to an instance of the class. Make sure you understand this point before continuing.
The init method also returns a value—namely, the initialized object. You store the return value in your Fraction variable myFraction.
The two-line sequence of allocating a new instance of class and then initializing it is done so often in Objective-C that the two messages are typically combined, as follows:
myFraction = [[Fraction alloc] init];
This inner message expression is evaluated first:
[Fraction alloc]
As you know, the result of this message expression is the actual Fraction that is allocated. Instead of storing the result of the allocation in a variable, as you did before, you directly apply the init method to it. So, again, first you allocate a new Fraction and then you initialize it. The result of the initialization is then assigned to the myFraction variable.
As a final shorthand technique, the allocation and initialization is often incorporated directly into the declaration line, as in the following:
Fraction *myFraction = [[Fraction alloc] init];
We use this coding style often throughout the remainder of this book, so it's important that you understand it. You've seen in every program up to this point with the allocation of the autorelease pool:
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Here an alloc message is sent to the NSAutoreleasePool class requesting that a new instance be created. The init message then is sent to the newly created object to get it initialized.
Returning to Program 3.2, you are now ready to set the value of your fraction. These program lines do just that:
// Set fraction to 1/3 [myFraction setNumerator: 1]; [myFraction setDenominator: 3];
The first message statement sends the setNumerator: message to myFraction. The argument that is supplied is the value 1. Control is then sent to the setNumerator: method you defined for your Fraction class. The Objective-C system knows that it is the method from this class to use because it knows that myFraction is an object from the Fraction class.
Inside the setNumerator: method, the passed value of 1 is stored inside the variable n. The single program line in that method stores that value in the instance variable numerator. So you have effectively set the numerator of myFraction to 1.
The message that invokes the setDenominator: method on myFraction follows next. The argument of 3 is assigned to the variable d inside the setDenominator: method. This value is then stored inside the denominator instance variable, thus completing the assignment of the value 1/3 to myFraction. Now you're ready to display the value of your fraction, which you do with the following lines of code from Program 3.2:
// Display the fraction using the print method NSLog (@"The value of myFraction is:"); [myFraction print];
The NSLog call simply displays the following text:
The value of myFraction is:
The following message expression invokes the print method:
[myFraction print];
Inside the print method, the values of the instance variables numerator and denominator are displayed, separated by a slash character.
The message in the program releases or frees the memory that was used for the Fraction object:
[myFraction release];
This is a critical part of good programming style. Whenever you create a new object, you are asking for memory to be allocated for that object. Also, when you're done with the object, you are responsible for releasing the memory it uses. Although it's true that the memory will be released when your program terminates anyway, after you start developing more sophisticated applications, you can end up working with hundreds (or thousands) of objects that consume a lot of memory. Waiting for the program to terminate for the memory to be released is wasteful of memory, can slow your program's execution, and is not good programming style. So get into the habit of releasing memory when you can right now.
The Apple runtime system provides a mechanism known as garbage collection that facilitates automatic cleanup of memory. However, it's best to learn how to manage your memory usage yourself instead of relying on this automated mechanism. In fact, you can't rely on garbage collection when programming for certain platforms on which garbage collection is not supported, such as the iPhone or iPad. For that reason, we don't talk about garbage collection until much later in this book.
It seems as if you had to write a lot more code to duplicate in Program 3.2 what you did in Program 3.1. That's true for this simple example here; however, the ultimate goal in working with objects is to make your programs easier to write, maintain, and extend. You'll realize that later.
Let's go back for a second to the declaration of myFraction
Fraction *myFraction;
and the subsequent setting of its values.
The asterisk (*) in front of myFraction in its declaration says that myFraction is actually a reference (or pointer) to a Fraction object. The variable myFraction doesn't actually store the fraction's data (that is, its numerator and denominator values). Instead, it stores a reference—which is a actually a memory address—indicating where the object's data is located in memory. When you first declare myFraction as shown, its value is undefined as it has not been set to any value and does not have a default value. We can conceptualize myFraction as a box that holds a value. Initially the box contains some undefined value, as it hasn't been assigned any value. This is depicted in Figure 3.2.
Figure 3.2 Declaring
Fraction *myFraction;
When you allocate a new object (using alloc, for example) enough space is reserved in memory to store the object's data, which inclues space for its instance variables, plus a little more. The location of where that data is stored (the reference to the data) is returned by the alloc routine, and assigned to the variable myFraction. This all takes place when this statement in executed in Program 3.2.
myFraction = [Fraction alloc];
The allocation of the object and the storage of the reference to that object in myFraction is depicted in Figure 3.3:
Figure 3.3 Relationship between myFraction and its data
Notice the directed line in Figure 3.3. This indicates the connection that has been made between the variable myFraction and the allocated object. (The value stored inside myFraction is actually a memory address. It's at that memory address that the object's data is stored.)
Subsequently in Program 3.2, the fraction's numerator and denominator are set. Figure 3.4 depicts the fully initialized Fraction object with its numerator set to 1 and its denominator set to 3.
Figure 3.4 Setting the fraction's numerator and denominator
The next example shows how you can work with more than one fraction in your program. In Program 3.3, you set one fraction to 2/3, set another to 3/7, and display them both.
Program 3.3.
// Program to work with fractions – cont'd #import <Foundation/Foundation.h> //---- @interface section ---- @interface Fraction: NSObject { int numerator; int denominator; } -(void) print; -(void) setNumerator: (int) n; -(void) setDenominator: (int) d; @end //---- @implementation section ---- @implementation Fraction -(void) print { NSLog (@"%i/%i", numerator, denominator); } -(void) setNumerator: (int) n { numerator = n; } -(void) setDenominator: (int) d { denominator = d; } @end //---- program section ---- int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Fraction *frac1 = [[Fraction alloc] init]; Fraction *frac2 = [[Fraction alloc] init]; // Set 1st fraction to 2/3 [frac1 setNumerator: 2]; [frac1 setDenominator: 3]; // Set 2nd fraction to 3/7 [frac2 setNumerator: 3]; [frac2 setDenominator: 7]; // Display the fractions NSLog (@"First fraction is:"); [frac1 print]; NSLog (@"Second fraction is:"); [frac2 print]; [frac1 release]; [frac2 release]; [pool drain]; return 0; }
Program 3.3. Output
First fraction is: 2/3 Second fraction is: 3/7
The @interface and @implementation sections remain unchanged from Program 3.2. The program creates two Fraction objects, called frac1 and frac2, and then assigns the value 2/3 to the first fraction and 3/7 to the second. Realize that when the setNumerator: method is applied to frac1 to set its numerator to 2, the instance variable frac1 gets its instance variable numerator set to 2. Also, when frac2 uses the same method to set its numerator to 3, its distinct instance variable numerator is set to the value 3. Each time you create a new object, it gets its own distinct set of instance variables. Figure 3.5 depicts this.
Figure 3.5 Unique instance variables
Based on which object is getting sent the message, the correct instance variables are referenced. Therefore, here frac1's numerator is referenced whenever setNumerator: uses the name numerator inside the method:
[frac1 setNumerator: 2];
That's because frac1 is the receiver of the message.