Xcode is helpful in setting up the skeletal structure of your class
files, including the inheritance tree. Now is the time to follow along and
create the DGCar
class files (and fill
them in as the chapter progresses).
Begin by selecting the Classes folder in the Files and Groups panel in the Workbench project window. Selecting this folder as a starting point causes Xcode to insert the files you’re about to create within the Class group. To see the next menu, you have two options:
Click to select the Classes folder and pull down the Action menu (the one with the gear icon).
Use your choice of secondary click (e.g., Ctrl-click, right-click with a two-button mouse, or two-finger-click with a trackpad) on the Classes folder to reveal the context-sensitive menu.
Choose Add to view the choices of new items you can create. Select New File, as shown in Figure 4-5.
A New File window opens, shown in Figure 4-6. For the simple class
being created here, choose Cocoa Touch Class in the left column, then
Objective-C class in the top right group. Next, make sure you specify that
this will be a subclass of NSObject
.
This will cause Xcode to fill in as many pieces as it can into the
skeleton files, including the superclass name. Click the Next
button.
The final step in the creation of the set of partially prewired class files is to name the class, shown in Figure 4-7. It is here where you assign a name to the class. The default file name includes the correct .m extension. The name you enter into the top field becomes not only the name of the two class files, but also the name of the class, which Xcode inserts into the class file templates. Xcode fills in everything else in this dialog box, but be sure to enable the checkbox next to the instruction to also create the .h header file for the class. Then click the Finish button.
Et voilà, your two class files are now created and inserted into the Class group, as shown in Figure 4-8. The new header file (below the comments) consists of the following code:
#import <Foundation/Foundation.h> @interface DGCar : NSObject { } @end
Notice that the @interface
directive is balanced by an @end
directive, which is always at the end of the header file. In between are
two sections for your code. The sections are labeled with placeholders
in the following code fragment:
#import <Foundation/Foundation.h> @interface DGCar : NSObject {// instance variable declarations here
}// method declarations here
@end
These two sections are for declarations—almost a table of contents for the variable and method names to be assigned values and defined in detail within the companion implementation file.
For purposes of this demonstration, the DGCar
class is designed to be parallel to the
JavaScript Car
object described
earlier in this chapter. The JavaScript object has three properties and
a method; the Objective-C version has three instance
variables (also called ivars, pronounced
EYE-varz) and two method definitions, as follows:
@interface DGCar : NSObject { NSString *make; NSString *model; NSString *year; } - (DGCar *)initWithCarMake:(NSString *)inputMake model:(NSString *)inputModel year:(NSString *)inputYear; - (NSString *)getFormattedListing; @end
You can probably recognize that the three JavaScript properties and three Objective-C instance variables serve similar purposes: when an instance of the object is created, that instance is designed to hold three values. Importantly, each instance can hold entirely different values without interfering with another—the essence of the object-oriented programming term, encapsulation.
There are, however, some important differences between the two
environments. First, you’ll notice that the Objective-C declarations
define each variable as belonging to the NSString
class. That is to say, each variable
(when eventually initialized) will itself be an instance of the NSString
class. Unlike JavaScript, when you
define an Objective-C variable as a particular type of data, the data
must be of that type and cannot change over the life of that variable.
You’ll learn more about data typing in Chapter 6.
An asterisk symbol indicates that the variable is what is known as a pointer to a place in memory where the object containing the value lives. The pointer notation appears with a variable when the variable is being declared, as happens in the header file. Chapter 6 has more information about pointers in Objective-C and how to use them.
Of the two methods defined in the DGCar
class header file, the second one,
getFormattedListing
, parallels the
like-named method in the JavaScript version. The leading hyphen tells
the compiler that the method is an instance method. A method declaration
ends with a semicolon.
In Objective-C, all method declarations must specify the type of
data returned when the method finishes running. The data type is placed
inside parentheses ahead of the method name. The getFormattedListing
method returns a value
that is of class NSString
(more
explicitly, a pointer to an NSString
object instance). As you’ll learn later, not all methods return a value
(just as not all JavaScript methods or functions return values). In
those cases, place the keyword void
in the parentheses where the data type goes.
Look now at the other method. Based on its name, it certainly
appears to have something to do with initialization. The original
JavaScript object constructor function accepts three arguments whose values are
assigned to object properties at the same time an instance of the object
is created. That same operation takes place in the Objective-C
initialization method for this class. In Objective-C syntax, you can
construct a method definition to accept arguments. Each argument must
have its data type declared
(again, covered in more detail in Chapter 6) and the name of a
variable to which the value is assigned when the method is called (just
like the parameter variables in the JavaScript constructor function’s
parentheses). The first argument comes after the method name, separated
from it by a colon. Subsequent arguments (if any) have individual,
explicit labels, which must have a space or other whitespace character
(such as a carriage return) between the end of the previous argument’s
parameter variable and the start of the label. Thus, the second argument
of the initialization method is named model
and its value must be of type NSString
. The way you refer to a method when
writing or speaking about it is to include all of the argument labels
and colons (if any). Therefore, the initialization method is technically
referred to as:
initWithCarMake:model:year:
One reason the argument labels are so important is that, unlike in
JavaScript, you can reuse a method’s name to define a completely
different method with a different set of arguments. Therefore, in this
same DGCar
class, you could define an
additional method as the following and there would be no
confusion:
initWithCarMake:model:year:listPrice:
The number of arguments and their labels in a statement that calls the method dictate the precise method being called.
While I’m on the subject of calling methods, Objective-C triggers
the execution of methods based on a model that is different from what
you use in JavaScript. Because each Objective-C object instance is
conceptually a separate entity, the way objects communicate with each
other is to send messages to each other. A message
may have the feel of invoking an object’s method, but there are
important differences. For instance, if an object doesn’t understand a
message, the message is ignored and the object simply returns nil
to the sender. Therefore, get in the habit
of using the “message” terminology, such as, “Object A sends the
myMethodWithArgument1:andArgument2:
message to Object B.”
One advantage of the argument labels in method definitions is that it’s much easier to understand what the values being passed mean. In the JavaScript version, you just pass three strings and hope you have them in the desired order as needed by the order of the parameter variables in the constructor function. In Objective-C, however, the labels guide you to precisely how you should use each value. Importantly, the order of the labels must be the same in the method definition and the statement sending the message to that method. Just because they have labels doesn’t mean you can mix up the order at will.
Speaking of labels, don’t be afraid to be verbose in devising label names that really define what they’re for. In fact, that goes for everything in Objective-C for which you need to assign a name. In JavaScript and web coding, you are very mindful that each character you apply to a variable or method name means a character that must make the journey from server to browser, occupying Internet bandwidth. But in the compiled Objective-C world, the compiler reduces those long names to very compact tokens. Long names in Objective-C contribute to the readability of your code (but you should not rely on them as replacements for good commenting).
The implementation file, which holds the @implementation
section, is where you write
the code that executes when an instance of the class is created and
thereafter. Look first at the empty DGCar.m file that Xcode generated for you
(omitting the top comments section):
#import "DGCar.h" @implementation DGCar @end
Xcode automatically fills in two items. The first is the #import
preprocessor directive. Whenever a
class definition is divided into separate header and implementation
files, the implementation file must instruct the preprocessor to import
the companion header file. Notice that for importing class header files
that are not frameworks, the header file name is in quotes rather than
the angle brackets used for importing frameworks (as was done in the
header file). The difference between quotes and brackets is that quotes
instruct the preprocessor to start looking for the file within the same
directory as the current implementation file. Since framework files are
stored elsewhere, the angle brackets tell the preprocessor to skip the
current directory.
If the @interface
and @implementation
sections were in the same
file, both sections would already be together, meaning the @implementation
section could see all the
declarations in the @interface
section and no importing would be necessary. Despite the apparent
simplicity of having both sections in one file (especially if you need
to make changes to method names or arguments during development), don’t
fight Xcode’s will in generating header/implementation file pairs for
your classes.
The blank @implementation
section Xcode generates is even simpler than the blank @interface
section shown earlier. There are no
curly-braced subsections to deal with. Almost all of your code goes
between the @implementation
and
@end
compiler directives.
Now we’ll inspect the details of a method definition and see how
the Objective-C form compares to what you know about JavaScript
methods. The two versions described in this chapter share the same
basic name, getFormattedListing
.
The Objective-C version is as follows:
- (NSString *)getFormattedListing { NSString *result = @""; result = [NSString stringWithFormat:@"[%@] %@ %@", year, make, model]; return result; }
Many things about this definition will look familiar to you. Compare the two language formats for method definition structure:
JavaScript:
functionmethodName
() { //Statements
}
Objective-C:
- (returnType
)methodName
{ //Statements
}
An Objective-C instance method definition begins with a hyphen symbol, followed by the return value type in parentheses and the method name. A block of statements to execute when the object receives a message matching the method name is contained inside a set of curly braces. Different programmers (in JavaScript, too) adhere to different styles of curly brace placement for blocks. Some place the left curly brace on the same line as the method name; others place the left brace at the start of the next line. Use the style you prefer.
The job of the getFormattedListing
method is to read the
values of the instance variables, insert them into a string, and
return the entire string as an NSString
type. It begins by creating an
empty NSString
object named
result
. The second statement sends
a message to the NSString
class
(not an instance) to invoke one of its class methods to plug the ivar
values into placeholders within a string (more about formats in a
moment). This NSString
message
returns a new NSString
object,
which is assigned to result
, the
value returned at the end.
You can see in the second statement of getFormattedListing
what an Objective-C
message looks like (the
expression to the right of the assignment (=
) operator). Forget everything you know
about JavaScript method-calling syntax, and get ready for the Objective-C way of doing things. Sending
a message requires at least two pieces of information:
A reference to the intended recipient object, i.e., the message’s receiver
The name of method to be called (plus any arguments and passed values)
These pieces are embedded inside square brackets in the following format:
[receiver method
]
To send a message containing a single argument, the syntax format is as follows:
[receiver method
:argument
]
Each additional argument consists of a label and a value, separated by a colon, as in the following format:
[receiver method
:argument labelA
:argumentA labelB
:argumentB
]
You can add one or more spaces around the square brackets if it
helps with your code readability. I predict that until you get used to
the square bracket notation, you will inevitably start writing
messages without them because of your experience with JavaScript
syntax. Additionally, every message includes a receiver, even if the
message you’re sending aims to invoke a method in the current class
definition. In those cases (as you will see many times later in the
book), the receiver is—you guessed it—self
, as in the following message, which
calls a loadNewData
method defined
elsewhere in the current class (and which does not return a
value):
[self loadNewData];
Every Objective-C object derived from NSObject
inherits from that base object the
init
method. That method is used as
part of the process to generate an instance of the object (in addition to memory
allocation, which you’ll learn about in Chapter 6). If your custom
class does nothing special when it creates an instance of itself,
there is no need for you to define an init
method in your class: the object will
receive the init
message and
automatically use the init
method
defined for its superclass, NSObject
. That’s how inheritance and message
passing work.
In the case of the DGCar
class defined here, its code follows the model in the JavaScript
version, in that whenever an object instance is created, that instance
is handed three string values to be assigned to instance variables. To
accommodate that, the DGCar
class
defines a customized initialization method, named initWithCarMake:model:year:
. The definition
of that method follows:
- (DGCar *)initWithCarMake:(NSString *)inputMake model:(NSString *)inputModel year:(NSString *)inputYear {
// If initialization of the superclass succeeds, // then assign instance variables if (self = [super init]) { make = inputMake; model = inputModel; year = inputYear; }
return self; }
The method definition signifies with the leading hyphen that it
is an instance method. Initialization methods always return a value
that is a reference to the object instance. You may have seen this
same concept in JavaScript constructor functions that end with a
return this
statement: the
constructor function returns a reference to the instance object just
created. Therefore, the initialization method for DGCar
returns a reference to the very same
object instance being created. The asterisk indicates that the value
is a pointer to the object instance in memory (pointers are covered in
Chapter 6).
When an Objective-C method accepts arguments, each argument is
assigned to a variable—the same
way JavaScript function arguments are assigned to parameter variables.
The difference in Objective-C is that you must specify the data type
for each one. Therefore, the car make must be passed to the DGCar
initialization method as an NSString
type of object. The same goes for
the other two arguments. Just as the method returns a pointer to the
object instance, the incoming arguments are assigned to variables that
hold pointers to their locations in memory. Thus, the (NSString *)
data type designations all have
asterisks. If you omit or forget the asterisks, the compiler will
complain that the argument is defined with an incorrect data
type.
Because the DGCar
class has
its own initialization method, the first task for that method is to
send the “normal” init
message to
the NSObject
superclass. You don’t
necessarily need to know everything the method does in the superclass,
but it is vital to completing the object’s instantiation. And, just as
the DGCar
class’s initialization
method returns a reference to the instance being created, so does the
NSObject
’s init
method. The DGCar
method needs to capture that reference
and apply it to itself. The way to do that is to assign the value returned by
the NSObject
’s init
method to a DGCar
property that references the instance
object being defined by the current class (self
):
self = [super init];
Notice that this action is embedded within an if
condition. The code confirms that the
root object initialization was successful before continuing with the
subclass initialization. If the superclass initialization were to
fail, the condition expression would evaluate to nil
, causing the nested statements to be
skipped. Performing these kinds of success tests is good practice,
even if the likelihood of failure approaches zero.
When the superclass initialization succeeds, parameter variables are assigned to the three instance variables that had been declared in the class’s interface. When you write the code in Xcode, the editor color codes instance variables that have been correctly declared (the default is green, but you can change color coding in Preferences). It is vital that the data types of the parameter variables match exactly the data types of the instance variable declarations. Xcode closely checks the consistency of data types as it builds an app. Mistakes will be highlighted before the app ever reaches the simulator.
The last statement of the DGCar
initialization method returns a
reference to the current instance—self
. A return value’s data type must match
the data type declared at the start of the method definition. In this
case, the self
keyword references
an instance of the DGCar
class
being initialized here. The returned value could also be nil
, which would occur if the superclass
initialization failed. As you’ll see in a moment, the reference to a
successfully created instance will be assigned to a variable elsewhere
so that the instance can receive
further messages.
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.