Integrating the DGCar Class into Workbench

After you have entered the customized code into the DGCar header and method files, it’s time to integrate the class into Workbench and see it in action. The class has already been added to the Workbench project (when Xcode created the files), so click the WorkbenchViewController.m file to add code to the runMyCode: method that is to act with the DGCar class when you click the button in the simulator.

The first task is to add an import directive to the WorkbenchViewController implementation. You can put it above or below the existing #import statement, because neither depends on the other:

#import "WorkbenchViewController.h"
#import "DGCar.h"

The only changes to the rest of the implementation file are inside the runMyCode: method. Replace the original NSLog() function with four new statements:

- (IBAction)runMyCode:(id)sender {
    DGCar *myCar1 = [[DGCar alloc] initWithCarMake:@"Chevrolet"
                                             model:@"Malibu" year:@"2007"];
    DGCar *myCar2 = [[DGCar alloc] initWithCarMake:@"Ford"
                                             model:@"Taurus" year:@"2010"];

    NSLog(@"I used to drive a:%@", [myCar1 getFormattedListing]);
    NSLog(@"I now drive a:%@", [myCar2 getFormattedListing]);
}

Warning

This method also needs some memory management, but I’ll reserve that discussion until Chapter 6.

Now that you have a little more experience with Objective-C methods, I can address finer details of the runMyCode: method, which you added to the project in Chapter 3. This method, you’ll recall, runs in response to the Touch Up Inside event in the button that appears on the screen. Events trigger actions, and those immediate actions don’t return values. Such is the case of the runMyCode: method. Normally, a method that does not return a value specifies void as the return type (in parentheses before the method name). But because we wanted to associate this method with a button in Interface Builder, we specified the return type as IBAction. From a data typing point of view, IBAction is the same as void, meaning the method returns no value. The IBAction type (especially in the header file declaration) tells Xcode to be ready to connect with Interface Builder and offers the method as a choice when it comes time to connect the event to the code. You saw how runMyCode: “magically” appeared in the pop-up list of items when making the connection in Chapter 3 (see Figure 3-18 as a reminder).

The runMyCode: method has one argument, which is assigned to the parameter variable sender. Although I have chosen to compose this IBAction method with one argument, you can use one of three action method formats, as follows:

- (IBAction)methodName
- (IBAction)methodName:(id)sender
- (IBAction)methodName:(id)sender (UIEvent *)event

Your choice depends on whether you expect your action method to need a reference to the user interface control (the sender) and to more details about the event. The UIEvent object is analogous to the event object that is passed to JavaScript/DOM event handler functions. For Workbench, the sender argument is a reference to the button that the user clicks, and I chose this format for demonstration purposes. What may be confusing at first is that the data type is simply id, rather than some UIKit type, such as UIButton (the type of button used in the project). You’ll learn about the id data type in Chapter 6—it’s a cool feature of Objective-C.

Creating Object Instances

Each of the first two statements in the runMyCode: method creates an instance of the DGCar class (just as the JavaScript example created two instances of the car object):

    DGCar *myCar1 = [[DGCar alloc] initWithCarMake:@"Chevrolet"
                                             model:@"Malibu" year:@"2007"];
    DGCar *myCar2 = [[DGCar alloc] initWithCarMake:@"Ford"
                                             model:@"Taurus" year:@"2010"];

On the left side of the assignment operator are the variables that hold pointers to the object instances. Each is of data type DGCar. Notice that the data type of a variable being declared (as in the header file) or being assigned a value through an assignment operator is not enclosed in parentheses. The right side of the operator must evaluate to a DGCar object instance.

To reach the object instance value, the righthand expression sends two messages nested inside each other. As in JavaScript, when expressions are nested, the most deeply nested expression evaluates first. In this case, it is the [DGCar alloc] message. You can visualize that the nested message replaces itself with its result, after which the outer message executes.

The alloc method belongs to the NSObject superclass, and its job is to reserve a chunk of memory for use by the object instance. Because the DGCar class definition does not have an alloc method defined for it (Apple cautions programmers against overriding this method in custom classes), the message passes up the inheritance chain until it finds a match—in this case, one generation higher in the NSObject superclass. As you will learn in Chapter 6, whenever you allocate memory for an object via the alloc method, you are also responsible for recovering that memory when you’re done with the object.

NSObject’s alloc method returns a bare-bones instance of the object with a clean memory slate. To complete the instantiation, you must send the init method to this bare-bones instance so the various class members (instance variables and methods) can be written to the memory reserved for the instance. The value returned by [DGCar alloc] becomes the receiver of the initWithCarMake:model:year: message. Recall that the DGCar class sends the init method to NSObject as part of the custom initialization method. Thus, the custom initialization method of DGCar is killing multiple birds with a single call, causing the superclass’s init method to be called and assigning values to the instance’s three instance variables.

Once the DGCar class instances exist (myCar1 and myCar2), you can send them whatever other messages you have defined for them. You’ve defined only one other method, getFormattedListing, to stay in sync with the JavaScript version. To invoke that method for myCar1, the message is:

[myCar1 getFormattedListing]

For the purposes of the Workbench app, you’ll display the value returned by that method to the Xcode project console window via the NSLog() function, introduced in Chapter 3. You need to plug that returned value into the argument of NSLog().

NSLog() and String Formats

Recall that the initial NSLog() statement put into Workbench was as follows:

NSLog(@"Button was pressed.");

The argument is an Objective-C string literal, which begins with the @ symbol, followed by the quoted string of characters. But if you want to combine fixed text with values stored in variables or returned by methods, you can use string format placeholders, or specifiers. A specifier consists of a percent symbol (%) followed by a character or symbol indicating the type of data to be sent to the console. If the value is an object, the specifier combination is %@. Values to be plugged into the specifier appear after the quoted string, separated by a comma:

NSLog(@"I used to drive a:%@", [myCar1 getFormattedListing]);

Conveniently, you can use the same format specifier to display the contents of a wide range of object types. That’s because most Cocoa Touch object classes include a description method, which returns a meaningful string representing the object or its data (e.g., the contents of an NSArray object). If you’re looking to get a numeric value, however, you need some of the other specifiers, such as %d for an integer value or %f for a float value. Chapter 8 dives deeper into format specifiers.

You are free to stack up multiple specifiers and their fillers in a single NSLog() argument. The following statements present an alternative version that plugs in two strings derived from the same instance methods as used earlier and includes some escape carriage return characters (\n) to make the output display on three lines:

NSString *myCar1Description = [myCar1 getFormattedListing];
NSString *myCar2Description = [myCar2 getFormattedListing];
NSLog(@"My cars:\nThen - %@\nNow - %@", myCar1Description, myCar2Description);

This is how you will use NSLog() frequently when experimenting with Objective-C and Cocoa Touch in the Workbench app—using the Xcode console window to examine results or intermediate values.

Warning

Exercise moderation in distributing NSLog() functions throughout an app, and prepare for the day when you release the app. Although NSLog() functions don’t display content visible to the casual iPhone or iPad user, too many of them in the wrong places can slow down your final app. Alternatively, search the Web for iphone debug macro to see many ways to customize Xcode to display log statements only while debugging a project.

Running the Code

After you have made the modifications to the runMyCode: method in WorkbenchViewController.m file, you are ready to build and run it in the simulator. If the console window is not open, choose Console from Xcode’s Run menu. Then click the Build and Run button. When you click on the Workbench app’s button, you should see the results as shown in Figure 4-9 (your timestamps will be different, of course).

Viewing the results in the console window

Figure 4-9. Viewing the results in the console window

What About Accessing Instance Variables?

You may be wondering how the runMyCode: method can get or set individual instance variables in one of the objects. In JavaScript, it’s as easy as using the dot syntax to reference the object property, either for reading or assignment:

// setting a JavaScript instance variable
var car1Year = car1.year;
car2.year = "2009";

In Objective-C you have two ways to get and set instance variables: the traditional way and the modern (Objective-C 2.0) way. I’ll describe only the traditional way in this chapter, while Chapter 8 will show you the modern way.

If you design a class so that other classes of objects need to access its instance variables, the traditional approach calls for creating a matching set of getter and setter methods. In other words, the methods act as gateways to reading and writing values of instance variables. Doing this, of course, requires quite a bit more source code, but the result is an instance that can potentially be more reusable in other scenarios.

To demonstrate how this works, I’ll create a new class named DGCarAlternate (this will allow DGCar and DGCarAlternate to coexist side by side in the Workbench project without colliding with or relying upon each other). Example 4-4 shows the header file for the DGCarAlternate class. Six new method declarations (new items in bold) are for the getters and setters of the three instance variables (one pair for each variable). Notice that the method names for the getters are the same as the instance variable names. Additionally, notice that the setter method names begin with lowercase set, followed by an initial-capitalized variable name, as in setMake:.

Example 4-4. DGCarAlternate.h file

#import <Foundation/Foundation.h>

@interface DGCarAlternate : NSObject {

    NSString *make;
    NSString *model;
    NSString *year;

}

// Getters and Setters
- (NSString *)make;
- (void)setMake:(NSString *)newMake;
- (NSString *)model;
- (void)setModel:(NSString *)newModel;
- (NSString *)year;
- (void)setYear:(NSString *)newYear;

- (NSString *)getFormattedListing;

@end

Definitions of the actual methods are in the implementation file, as shown in Example 4-5 (new items are in bold).

Example 4-5. DGCarAlternate.m file

#import "DGCarAlternate.h"


@implementation DGCarAlternate

// Getter and Setter Methods
- (NSString *)make {
    return make;
}

- (void)setMake:(NSString *)newMake {
    make = newMake;
}

- (NSString *)model {
    return model;
}

- (void)setModel:(NSString *)newModel {
    model = newModel;
}

- (NSString *)year {
    return year;
}

- (void)setYear:(NSString *)newYear {
    year = newYear;
}

// Return formatted listing string
- (NSString *)getFormattedListing {
    NSString *result = @"";

    result = [NSString stringWithFormat:@"[%@] %@ %@", year, make, model];

    return result;
}

@end

Notice that I removed the custom initialization method from the DGCarAlternate class. Why? Because values will be assigned to the instance variables via setter methods rather than when the instance is allocated and initialized—merely a style choice for this demonstration. Example 4-6 shows the application of the DGCarAlternate class to the runMyCode: method in Workbench.

Example 4-6. The Workbench runMyCode: method designed for getters and setters

#import "WorkbenchViewController.h"
#import "DGCarAlternate.h"

@implementation WorkbenchViewController

- (IBAction)runMyCode:(id)sender {
    DGCarAlternate *myCar1 = [[DGCarAlternate alloc] init];
    DGCarAlternate *myCar2 = [[DGCarAlternate alloc] init];

    [myCar1 setMake:@"Chevrolet"];
    [myCar1 setModel:@"Malibu"];
    [myCar1 setYear:@"2007"];

    [myCar2 setMake:@"Ford"];
    [myCar2 setModel:@"Taurus"];
    [myCar2 setYear:@"2010"];

    NSLog(@"I used to drive a:%@", [myCar1 getFormattedListing]);
    NSLog(@"I now drive a:%@", [myCar2 getFormattedListing]);
}
...
@end

Notice that objects must still be instantiated via the alloc and init methods. But both messages pass up the inheritance chain through DGCarAlternate to the NSObject superclass (there is no alloc or init method defined in DGCarAlternate to intercept the messages).

Now that I have access to the instance variable getters, I can obtain those values individually from the runMyCode: method. For example, I can create a different format for the results sent to the console, as in the following:

NSLog(@"I used to drive a %@ %@ made in %@.", [myCar1 make],
    [myCar1 model], [myCar1 year]);

The setters also allow me to change the values of instance variables. Therefore, I can mix the first version of this class (with the custom initialization method that assigned values as the instance is being created) with additional code that changes one or more variables after instantiation.

Get Learning the iOS 4 SDK for JavaScript Programmers now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.