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.
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()
.
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.
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).
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.