We discussed Objective-C briefly in the last chapter. In this chapter, we’ll go into a lot more depth.
The Objective-C language was invented by Brad Cox in the early 1980s and was based on the object-oriented principles of SmallTalk. Cox wanted to create a computer environment that could be used to build software-ICs — software components that could be used to create large programs in much the same way that discrete integrated circuits are used to create computers. Cox wrote a book, Object-Oriented Programming: An Evolutionary Approach, in which he outlined his strategy of grafting object-oriented technology onto existing programming languages (e.g., C), rather than creating fundamentally new languages (e.g., Simula and SmallTalk). And he founded the Stepstone Corporation to bring his brainchild to the market.
Objective-C is based on two important principles. First, Cox wanted to create a language that offered much of the object-oriented programming power that existed in SmallTalk. Specifically, he wanted a language in which both classes and instances of classes were objects, a language that allowed introspection, and a language that performed the runtime evaluation of messages. Second, he wanted a language that was easy to learn and as similar to C as possible. He came up with Objective-C.
Objective-C is quite similar to the ANSI C language, but it introduces a single new type, one new operator, and a few compiler directives. These additions are summarized in Table 4-2.
Table 4-2. New features in Objective-C
New feature |
Example |
Purpose |
---|---|---|
|
|
Includes a file if it has not been included before. Similar to
|
|
|
Pointer to an object. |
|
|
Messaging operator; sends a message to an object. |
|
|
Pointer to the current object. |
|
|
Pointer to the current object’s parent class. Allows a method implementation in a class to call another method implementation in the superclass. This is most commonly used when overriding method implementations. |
|
|
Marks the beginning of a class declaration. Usually appears in a
|
|
|
Marks the beginning of a class implementation. Usually appears in a
|
|
|
Marks the beginning of a protocol declaration. Usually appears in a
|
|
|
Tells the compiler that a class will be referenced before it is
defined. Similar to declaring a |
|
|
Notes the end of an |
|
|
Introduces a class method in an |
|
|
Introduces an instance method in an |
|
|
Used to create an unnamed NSString object. Equivalent to
|
An Objective-C
object
is a self-contained bundle of code containing data and procedures
that operate on that data. The data is stored in instance
variables
, and the procedures are called
instance methods
. For example, an NSWindow object,
which controls an on-screen window, contains a
frame
instance variable that stores the
window’s location on the computer’s
screen. The NSWindow object is self-contained, or
encapsulated,
in the sense that instance variables such as frame
are not directly accessible from outside the object; you can modify
them only by invoking the object’s methods. These
methods assure that all access to instance variables is carefully
controlled, which helps ensure the integrity of the instance data.
This whole process is sometimes called data
encapsulation
.
An Objective-C class is a template that defines characteristics that are common to all objects that are members, or instances , of that class. For example, the NSWindow class defines the instance variables and methods that comprise NSWindow objects. The NSWindow class also defines special class or factory methods for creating new objects.
Objective-C is different from other object-oriented languages, such as C++, in that Objective-C classes are objects themselves — you can send them messages and pass references to classes as arguments.
When a class creates an object using a class method, it sets up memory for a new data structure containing the instance variables defined by the class. It does not make a copy of the instance methods that the object can perform. There is only one copy of the instance methods, and they are stored as part of the class’s definition in the computer’s memory. These instance methods are shared by all instances of the class, which makes memory usage more efficient. This also means that every member of a class responds to a message in the same way. (This is not the case in some other object-oriented languages, where individual objects are allowed to “specialize” a class.)
For example, suppose that an application requires two on-screen
windows. The application will send two separate requests (messages)
to the NSWindow class to create two distinct NSWindow objects. Each
NSWindow object will contain its own class-defined data structure
with its own copies of the instance variables (e.g.,
frame
). If one of the NSWindow objects is asked to
perform the setFrame: action (which
changes the window’s origin and size), the window
object will go to the NSWindow class definition in memory for the
actual setFrame: code, but it will
change only the frame
instance variables in its
own data structure, not those in the other window on the screen.
Figure 4-2 shows an application with two windows on the screen; each window has a corresponding NSWindow object inside the computer’s memory, and each NSWindow object has its own set of instance variables but shares the same methods.
Inside the computer’s memory, objects are implemented as data structures that contain the instance variables as well as pointers to the objects’ classes. It’s the Objective-C runtime system that brings all this to life.
An Objective-C method is invoked by sending the object a message .[8] Objective-C messages are enclosed in square brackets, as follows:
[receiver message]
The receiver can be a class or an instance object, while message is a method name together with arguments. We will refer to the entire bracketed expression [receiver message] as a message as well, although some prefer to call it a message expression .
For example, suppose that you have an NSWindow object variable called
aWindow
. You can send aWindow
the message orderOut with this
statement:
[aWindow orderOut];
The terms method and message may appear to be used interchangeably and to mean the same thing, but they actually have slightly different meanings. A method is a procedure inside a class that’s executed when you send that class or an instance of that class a message. The method is executed when the object is sent the corresponding message. Indeed, the same message sent to objects of different classes will usually cause methods with different implementations to be invoked.
Tip
Although the phrase “sending a message” suggests concurrency, message invocations are similar to traditional C-language function calls. If your program sends a message to an object, that object’s corresponding method has to finish executing before your program can continue with other tasks.
One nice thing about Objective-C is that the same syntax is used for sending messages to both classes and instances. For example, this code sends the message alloc to the NSWindow class:
[NSWindow alloc]
while this code sends the display
message to the object pointed to by the object variable
aWindow
:
[aWindow display]
Messages can have arguments. For example, this code sends a message that has a single argument:
[aCell setIntValue:52];
This next line of code sends a message that has two arguments:
[myMatrix selectCellAtRow:5 column:10];
It tells the myMatrix
object variable to select
the cell of the matrix at position (5,10). The full name of the
message is selectCellAtRow:column:,
which is what you get when you remove the arguments and the spaces
from the message invocation. The message name contains all of those
letters and both colons.
By convention, class names usually begin with uppercase letters, while methods and instances begin with lowercase letters. This convention is occasionally violated, however, when using nonstandard case improves the readability of a program’s source code.
Objective-C adds one new data type, id, to the C programming language. An
id variable is a pointer to an object in the
computer’s memory. (The variable
myMatrix
in the previous section could have been
defined as an id variable.) You can think of an id as somewhat
analogous to the ANSI C void
*
pointer, but whereas a void
*
pointer can point to any kind of structure, an
id variable can point to any kind of object.
There is an important difference between a
void
*
pointer and an
id — a function that receives a void
*
pointer has no way of knowing what the pointer
really points to. On the other hand, Objective-C objects contain type
information, so it is possible for an Objective-C function to examine
an id and determine the kind of object that it points to, or
references.
Looking at an id pointer and figuring out what kind of object it points to is called introspection , and it happens often when an Objective-C program runs. The Objective-C language uses introspection to implement dynamic binding . When you send a message to an Objective-C object, the Objective-C runtime system literally hands that message to the object and asks the object “Which function call do you want to run in response?” Dynamic binding allows different objects to respond to the same message in different ways, which gives Objective-C programmers a tremendous amount of power and flexibility.
Similarities and differences between void
*
pointers and the id data type are summarized in
Table 4-3.
Table 4-3. Pointers to structures versus pointers to Objective-C objects
Characteristic |
Pointers to structures |
Pointers to Objective-C objects |
---|---|---|
Pointer type |
|
|
Sample declaration |
|
|
Size of pointer on 32-bit PowerPC microprocessor |
4 bytes |
4 bytes |
Points to |
Any kind of structure |
Any kind of object |
To determine the kind of object pointed to |
Impossible unless the type is encoded inside the structure itself |
Send the object a message — for example, [obj class] |
In the next section, we’ll continue our exploration of Objective-C by creating an actual class.
Note
The Objective-C runtime is extremely fast. Although it is true that Objective-C messages take somewhat longer to execute than do traditional C function calls or C++ member function dispatches, the actual amount of clock time is measured in microseconds — under normal circumstances, you should not be concerned with the overhead of an Objective-C method invocation. Remember that the Objective-C runtime that you are using can directly trace its lineage to a version that ran on a Motorola 68030 computer running at 25 MHz! It was plenty fast then; today’s computers are at least 20 times faster.
Don’t spend your time trying to “get around” the Objective-C runtime by looking for ways to replace Objective-C messages with traditional function calls. Instead, use the Objective-C runtime to your fullest advantage. It will save you time developing your application, allowing you to concentrate on issues of design. If your application seems to run slowly, this is almost certainly the result of poor design, not of the minor overhead caused by Objective-C method dispatches.
Suppose that a friend asks you to help debug a program that is supposed to help ninth-grade students in a chemistry course by drawing pictures of molecules. To draw the atoms on the computer’s screen, your friend has created a class called Circle. Instances of this class will be used to draw the atoms on the computer’s screen. The class is called Circle, rather than Atom, because your friend hopes to reuse this class for a graphics package that he is creating.
To perform the necessary functions, your friend has implemented in his program a variety of methods that respond to messages. Instances of the Circle class respond to an Objective-C drawSelf message that causes them to display themselves in the currently selected window. A second method, setRadius: , sets the circle’s size.
Let’s look at these methods in practice. At part of
the program your friend is debugging, there is a variable called
aCircle
that points to a particular circle that is
being acted upon. At this point in the code, the program can force
the circle to display itself with this excerpt of code:
[aCircle drawSelf];
Likewise, the radius of the circle can be set to 5.0 with this statement:
[aCircle setRadius:5.0];
There is no limit to the number of methods to which a class can respond. For example, your friend has implemented a method that can be used to set the center of the circle to a particular (x,y) coordinate:
[aCircle setX:32.0 andY:64.0];
Methods can also return values. In this example, your friend has implemented a method that allows the program to determine the x and y coordinates of the circle’s center. For example:
printf("aCircle centered at %f,%f\n", [aCircle x],[aCircle y] );
The methods setX:andY:
, x, and
y are called
accessor methods
because they give you access to a variable encapsulated inside the
aCircle
object. In this case, the methods
x and y return floating-point numbers (the values in
the instance variables), and the output would be:
aCircle centered at 32.0,64.0
Accessor methods free the programmer using a class from having to know the details of how the class is implemented. For instance, the Circle class might store the location of the circle as a center (x,y) and a radius (r). Alternatively, the Circle class might store the location of the circle as the bounding box (x1,y1) to (x2,y2), or as a bounding box with an origin at (x,y) and an extent (width,height). Each of these representations has certain advantages and disadvantages to the programmer implementing the Circle class. But as programmers using the Circle class, we don’t really want to know how it is implemented — we just want to be sure that it works properly.
Tip
When you create your own classes, you should first consider what kinds of accessor functions the programmers using the classes will require. The initial design of the class will often be dictated by the accessor methods, but if you use well-defined accessor methods, you will be able to change the implementation of your class without needing to make many other changes in your software.
Every computer language provides a facility for allocating and
initializing new regions of memory. In ANSI C, memory allocation is
done with the functions malloc( )
,
calloc( )
, and memset( )
. C++
allocates new objects with new. The
Objective-C
methods for allocating and initializing memory are alloc and init.
The alloc method is a class method: you send the message alloc to a class, and the class allocates the memory for that object and returns a pointer to the object that it just allocated. In our example, we will send the alloc method directly to the Circle class.[9] Because the alloc method creates a new object, it is often called a factory method .
The init method is an instance method; you send the message init to an object that was just allocated and the object initializes itself. So your friend’s program might have a bit of code in it that looks like this:
id aCircle; // declare object pointer aCircle = [[Circle alloc] init]; // create aCircle instance [aCircle setX:32.0 andY:64.0]; // set center of circle [aCircle setRadius:10.0]; // set radius of circle [aCircle drawSelf]; // display circle on screen
The aCircle
=
[[Circle
alloc]
init]
statement sends the alloc message to the Circle class, asking it
to allocate memory (create) a new Circle object. The alloc method returns the id of an
uninitialized Circle object. This object is then sent an init method, causing it to be initialized. The
init method returns the id of the
object that we are supposed to use. This id is usually the same as
the id that the alloc method
returned — but it is not always the same, which is why it is
important to nest the alloc and
init methods.
Another feature of Objective-C is that it allows you to tell the compiler that a pointer will point only to an object of a particular type of class (or one of its subclasses). For example, we could rewrite the previous code in this way:
Circle *aCircle= [[Circle alloc] init]; [aCircle setX:32.0 andY:64.0]; // set center of circle [aCircle setRadius:10.0]; // set radius of circle [aCircle drawSelf]; // display circle on screen
This notation is called static (strong) typing . The advantage of static typing is that the compiler can perform a limited amount of checking and can issue warnings if you seem to be sending a message to a class or an instance of the class that is not implemented.
Because initializing an object and setting its instance variables is a common operation, most Objective-C classes provide special-purpose initializers that perform both of these functions. Let’s say the Circle class has such an initializer, called initX:Y:radius: . Using this initializer, we might simplify the previous code fragment to look like this:
Circle *aCircle = [[Circle alloc] initX:32.0 Y:64 radius:10]; [aCircle drawSelf];
There are many kinds of messages that you can send to an object. These messages are defined in the class interface definitions.
To use a new class in your program,
you need some way to tell the Objective-C compiler the names of the
class, its instance variables, its methods, and the superclass from
which it is derived. This is done with a class
interface
— a fancy name for an included
file that is brought to the compiler’s attention
with the #import
directive.
The Connector class example we’ll use here has a relatively simple class interface, shown in Example 4-2.
Example 4-2. The Connector.h class implementation file
/* Connector.h: * The Connector class interface file */ #import <Foundation/NSObject.h> @interface Connector : NSObject { id start; id end; } + (id) connector; + (id) connectorFrom:(id)anObject to:(anObject); - (id) init; - (void) setStart:(id)aStart; - (void) setEnd:(id)anEnd; - (id) start; - (id) end; - (float) length; - (void) drawSelf; @end
The following line in Example 4-2 begins the class interface:
@interface Connector : NSObject
This line tells the compiler that we’re about to define the Connector class and that the Connector class inherits from the NSObject class. This means that each instance of the Connector class has a copy of the same variables that the NSObject objects have, and that they respond to the same messages as other NSObject instances. Connector objects also have additional variables and methods, as defined by the programmer who created the class. We’ll discuss inheritance in greater detail a bit later.
The next two lines in the example define the instance variables
(start
and end
) that every
Connector object contains.
The block of lines that begins with plus signs (+) and minus signs
(-) defines the class and instance methods of the class. Those lines
beginning with plus signs are class methods; you send the
corresponding message to the Connector class itself. Those beginning
with minus signs are instance
methods
; they are invoked by messages sent to
class instances. Following the plus or minus sign is a C-style cast
that shows the type that will be returned when the method runs. If no
type is declared, (id)
is assumed
(it’s the default).
Tip
You can have class and instance methods with the same name; the Objective-C runtime system automatically figures out if you are sending a message to a class or to an instance of that class.
Let’s skip over the class methods for now and focus on the instance methods. The init method should be familiar by now: that’s the method that initializes an instance of the Connector class that’s been allocated with the alloc method. The methods setStart: , setEnd: , start , and end are all accessor methods: they allow you to set and inspect the values of the Connector’s instance variables. The length method returns a floating-point value that corresponds to the distance between the centers of two objects. Finally, the drawSelf method can be sent to the Connector to ask it to draw itself.
Did you notice that Example 4-2 started with an #import
preprocessor directive, instead of the more traditional ANSI C
#include
? This was not a misprint.
The Objective-C #import
statement is similar to
C’s #include
statement, but with
an improvement: if the file specified in the
#import
statement has already been
#import
-ed, that file doesn’t get
#import
-ed a second time. This is an incredibly
useful feature, because it avoids all sorts of
“kludges” for which C
#include
files are notorious. Here is an example
of the type of kludge we mean:
/* kludge.h: * A kludgy C #include file */ #ifndef _ _KLUDGE_ _ #define _ _KLUDGE_ _ ... /* code that we wanted to include, but just once */ ... #endif
ANSI C #include
files typically check to see
whether some symbol (in this case, _ _KLUDGE_ _
)
is defined and, if it is not, define the symbol and process the rest
of the #include
file. This methodology is both
inefficient and dangerous. It is inefficient because every
#include
file is typically processed numerous
times. It is dangerous because different files can inadvertently have
the same _ _KLUDGE_ _
symbol defined, which causes
one of the files to prevent the contents of the other file from being
processed.
Objective-C’s #import
statement
actually does what programmers want done — it reads in the
contents of the file if the file has not previously been read. With
Objective-C, the previous example could be rewritten as simply:
#import <kludge.h>
When you are done using a piece of memory, it is polite to return the memory to the computer so that it can use that memory for other purposes. Well, it’s more than polite — if you don’t free memory when you’re done using it, your program will require more and more memory over time, and eventually it will run out of memory and crash. Let’s look at how that problem is handled in several programming environments:
- C/C++
In ANSI C, memory that is alloc-ed with
malloc( )
orcalloc( )
is freed with thefree( )
function. C++ usesnew
to create new objects anddelete
to free them. If you are a programmer who is using these languages, you need to manually keep track of all of your memory; when you no longer need a piece of memory, it’s your responsibility to free it. That’s not much of a problem for simple programs, but it can be a problem when objects are created in one part of your application and used in another part; frequently, objects end up never being freed, or being freed multiple times. If either of these things happens in a C or C++ program, the program will eventually crash.- Java
In contrast, the Java programming language does not have an explicit way to free memory. Instead, it has a garbage-collection system that automatically frees objects when they are no longer referenced anywhere in the running program. This eliminates the memory-management problems inherent in C and C++, but it creates a new class of problems. Garbage collection is almost impossible to implement efficiently, and it’s easy for a programmer to make a relatively minor mistake that prevents memory from ever being freed. Just ask any Java programmer!
- Objective-C
Cocoa has a third approach to memory management that is a hybrid of these two approaches. When you write a Cocoa program with Objective-C, each part of the application needs to notify the underlying system when it is using an object and when it is finished with an object. The underlying system maintains a reference count for each object, which keeps track of whether any other part of your program is using the same object. When the final part of your program releases the object, the object is automatically freed. From here on, we’ll concentrate on this approach.
When Objective-C objects are initialized, they are given a reference count of 1. When you’re done using an Objective-C object, you send it a release message. This message causes the object to decrement its reference count. If the reference count is decremented to 0, it’s time to free the object. In this case, the Cocoa runtime system sends the object a dealloc message.
The dealloc method is similar to
C’s free( )
function: send an
object the dealloc message and the
memory associated with the object is freed — the object literally
frees itself. But as a Cocoa programmer, you will never send the
dealloc message to an object. You
just send release messages.
The reverse of the release message is the retain message. This is the message that you send to an object to increment its reference count. If you create an Objective-C object that is going to be working with another object, that first object should retain the id of the second object. This will prevent the second object from being inadvertently dealloc-ed somewhere else in the program.
Let’s see this process in action. Remember the Connector class from Example 4-2? The Connector class draws a line from one object to a second object. What’s particularly clever about the Connector class is that it doesn’t know where it’s located — it simply knows the objects to which it is connected. It then asks each of these objects their position to determine where it should draw its line.
To set up a connector between the objects circle1
and circle2
, we might create a snippet of code
that looks something like this:
Connector *aConnector = [[Connector alloc] init]; [aConnector setStart:circle1]; [aConnector setEnd:circle2];
The implementation of the setStart: method might have a code fragment that looks like this:
start = [anObject retain];
When the retain
message is sent, the reference
count on the object pointed to by the variable
anObject
will be incremented. The id of this
object will then be assigned to the variable
start
, which is an instance variable within an
instance of the Connector class.
When the instance of the Connector class finishes working with this object, it will release it with a line of code that looks like this:
[start release];
When the release method is called, the reference count is decremented. If it is 0, the object will automatically be sent a dealloc message, which will cause the object to be freed. Remember, you should never send the dealloc message yourself.
Objective-C uses the @implementation
directive to tell the compiler that
the following methods are method implementations. Implementations are
stored in files that have the extension .m
. The
syntax of these files is somewhat similar to the syntax of the class
interface files. Example 4-3 contains an excerpt of
a sample Connector.m
implementation file.
Example 4-3. The Connector.m file, our first try
/* Connector.m: * The implementation of the Connector class */ #import "Connector.h" @implementation Connector -(void)setStart:(id)anObject { start = [anObject retain]; } -(void)setEnd:(id)anObject { end = [anObject retain]; } @end
Following the @implementation
directive are the
actual class methods that are being defined. The two methods in Example 4-3 each begin with a minus sign (-), indicating
that they are instance methods. In fact, these methods are accessor
methods, designed to set the values of the start
and end
instance variables.
At the end of the class methods, there is a line containing the
@end
directive. This tells the compiler
that you are done defining methods.
When your program is running, the Objective-C system knows to run these snippets of code if the setStart: or setEnd: message is sent to a Connector object (that is, an instance of the Connector class).
These methods are pretty good, but they both
contain a significant bug: they can leak memory if they are ever
called a second time. This is because both the setStart: method and the setEnd: method discard the old values for the
start
and end
variables without
first releasing them. So a better implementation for these methods
might look like this:
@implementation Connector -(void)setStart:(id)anObject { [start release]; start = [anObject retain]; } -(void)setEnd:(id)anObject { [end release]; end = [anObject retain]; } @end
(Notice that the newly added code is highlighted in bold; this is a convention that we will use throughout this book when we mix “old” code with “new” code to be inserted.)
Because the setStart: and setEnd: methods retain the object that is passed in as an argument, it is important that this object be released when it is no longer needed. We can force the Connector to do this by overriding the dealloc method in the Connector class:
-(void)dealloc { [start release]; [end release]; [super release]; }
This method will release the variables start
and
end
, then call [super
release]. This expression passes the release message to the superclass of the
Connector class — that is, the class from which the Connector
class is derived. We don’t yet know what that class
is — that information is contained in the Connector class
interface definition.
What about the length
method? This is a method that returns
the length of the connector, or the distance between the two objects.
It’s not an accessor method, because there is no
length
instance variable. In fact, the Connector
class has no idea where the connector object is actually located;
this information is stored in the objects pointed to by the
start
and end
instance
variables.
One way to implement the length method is like this:
- (float) length { float dx = [start x]-[end x]; float dy = [start y]-[end y]; return sqrt(dx*dx+dy*dy); }
As you learn to program in Objective-C, you’ll discover that it is common to implement one method by having the method send other messages.
In the previous example, there is an important method that the Connector class responds to that you do not see in the interface file. That method is the +alloc method — the method that creates new objects (or instances) of the Connector class. The plus sign (+) means that it’s a class method — a method that is invoked by a message you send to the Circle class itself, rather than to an instance of the class.
The Connector and Circle classes do not have their own +alloc methods. Instead, they inherit this method from their common superclass, the NSObject class. We won’t show the entire interface NSObject root class because it’s pretty big, but here is a small portion of it:
@interface NSObject { Class isa; } + (void)initialize; - (id)init; + (id)new; + (id)allocWithZone:(NSZone *)zone; + (id)alloc; - (void)dealloc; - (id)copy; - (id)mutableCopy; ... @end
As you can see, an NSObject has a single instance variable called
isa
. This variable is of type Class, which is a
typedef
for an ANSI C structure that contains the
class information for this object.
Every[10] class in Cocoa inherits from type
NSObject, and therefore every object contains this
isa
pointer to its class type. Likewise, every
class includes the class methods that are present in the NSObject
class. The most important of these class methods is +alloc, which allocates new objects of the
class.
The NSObject class is part of Cocoa’s Foundation class library. As this book progresses, we will explain more aspects of the NSObject class and the class methods that it contains. (If you are curious, you can put down this book now and read the documentation for the NSObject class.)
Two other important Objective-C classes that you will use often are
the
NSString and NSMutableString classes.
These two classes allow you to construct and manipulate strings that
are coded in standard 7-bit ASCII, 8-bit Unicode, 16-bit Unicode, or
the traditional Macintosh coding system. These classes provide for
practically everything you could ever want to do with a string,
including copying it, performing string searches, creating a
substring, formatting printing, and more. The vast majority of Cocoa
methods that expect a string as an argument use an NSString, rather
than a traditional ANSI C char
*
.
Because the NSString class is so widely used, Apple modified the
Objective-C compiler to make it easy to create these strings. Once
again, it’s done with the at sign
(@
). Whereas ANSI C uses a pair of double quotes
to create a byte array, Objective-C uses the at sign
(@
) followed by a pair of double quotes to create
an NSString. For example:
char *str = "this is an ANSI C string."; NSString *str2 = @"this is a Cocoa string.";
Strings created with the NSString class are immutable , meaning that they cannot be changed. If you want to be able to make changes to the string after you have created it, you need to use the NSMutableString class instead. In this example, we will create a string and then append a message to it:
NSMutableString *str3 = [[NSString alloc] init]; [str3 appendString:"This is how you build "]; [str3 appendString:"a Cocoa String."];
The Cocoa NSString class has nearly a dozen different initializers
that allow you to create an initial string from another string, from
traditional ANSI C strings, and even from
printf
-style formats. Consider these examples:
NSString *str4 = [[NSString alloc] initWithString:@"a String"]; NSString *str5 = [[NSString alloc] initWithCString:"a C String"]; NSString *str5 = [[NSString alloc] initWithFormat:@"3+3=%d",3+3];
If you want to print the value of an NSString, you should use the
NSLog( )
function. This function is similar to the
ANSI C printf
function, but with three important
differences:
Instead of taking a
char
*
as its first argument, it takes anNSString
*
.In addition to the standard
printf
formats, it understands%@
to print the object’s description.[11]In addition to printing the requested format, it also prints the date and time.
Example 4-4 shows a small program that illustrates
both string processing and the NSLog( )
function:
Example 4-4. A small example of NSString
#import <Cocoa/Cocoa.h> int main(int argc,char **argv) { int i; for (i=1;i<5;i++) { NSString *str1 = [[NSString alloc] initWithFormat:@"%d + %d = %d", i, i, i+i]; NSLog(@"str1 is '%@'",str1); [str1 release]; } return(0); }
Type in this program and save it in a file called
adder.m
. You can then compile the program as
follows:
localhost> cc -o adder adder.m -framework Cocoa
localhost>
Then you can run it:
localhost> ./adder
2002-02-13 08:27:10.752 adder[3004] str1 is '1 + 1 = 2'
2002-02-13 08:27:10.753 adder[3004] str1 is '2 + 2 = 4'
2002-02-13 08:27:10.753 adder[3004] str1 is '3 + 3 = 6'
2002-02-13 08:27:10.753 adder[3004] str1 is '4 + 4 = 8'
localhost>
As a Cocoa programmer, you will frequently write methods that need to return an object. You’ll also often want to create and use objects without having to worry about destroying the objects when you’re done. Cocoa makes both of these tasks easy with its memory-management system.
Function calls that return objects or allocated blocks of memory are the bane of programming languages such as C and C++. This is because it isn’t always clear where the objects or memory should be deallocated. Cocoa gets around this problem by having two different methods for releasing objects that have been retained — the release method and the autorelease method.
When you send an object a release message, the object’s reference count is immediately decremented. If the reference count reaches 0, the object is sent a dealloc message. The autorelease message does not cause the object’s reference count to be decremented immediately. Instead, it causes the object to be added to a list of objects in the current autorelease pool . Objects in the autorelease pool are sent a release message when the current autorelease pool is deallocated.
Typically, the Cocoa system creates an autorelease pool at the beginning of each pass through the event loop; this autorelease pool is released when the event is done being processed. If you’re writing a function or a method that returns an object, you can autorelease and then return the object. The caller to the function then has the option of either retaining that object itself, in which case the object will not be freed, or doing nothing, in which case the object will be freed when event processing is over.
Let’s see how this works in practice. Example 4-5 shows our NSString example rewritten to use the autorelease pool (NSAutoreleasePool). The pool is created before the loop starts and is released when the loop finishes executing.
Example 4-5. The NSString example rewritten to use the autorelease pool
#import <Cocoa/Cocoa.h> int main(int argc,char **argv) { int i; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; for (i=1;i<5;i++) { NSString *str2 = [NSString stringWithFormat:@"%d + %d = %d",i,i,i+i]; NSLog(@"The value of str1 is '%@'",str2); } [pool release]; return(0); }
Notice that this line of code from Example 4-4:
NSString *str1 = [[NSString alloc] initWithFormat:@"%d + %d = %d", i, i, i+i];
was replaced with this line:
NSString *str2 = [NSString stringWithFormat:@"%d + %d = %d", i, i, i+i];
These lines are not equivalent. In the first case, the object pointed
to by str1
is an allocated, initialized object
that has a string and a reference count of 1. In the second case, the
object pointed to by str2
is an allocated,
initialized object with a reference count of 1, but the
object’s id has further been added to the
NSAutoreleasePool. This code is actually equivalent to the following:
NSString *str2 = [[[NSString alloc] initWithFormat:@"%d + %d = %d",i,i,i+i] autorelease];
That is, the single method stringWithFormat: replaces the methods alloc, initWithFormat:, and autorelease. Many Foundation and Application Kit classes have class methods that return objects that have been autoreleased.
Don’t worry if this seems confusing. In subsequent chapters, we’ll use the autorelease system so much that it will be second nature to you by the time you’re finished with this book.
[8] The Mach operating system, upon which Mac OS X is based, offers another kind of messaging called Mach messages. Mach messages should not be confused with Objective-C messages.
[9] One of the interesting aspects of Objective-C is that the Circle class is itself an object. In C++, classes are not themselves objects.
[10] Actually, virtually every Objective-C class inherits from NSObject. There are a few special-purpose classes that do not, but these classes are not important for the purpose of this discussion.
[11] In the case of
the NSString class, the description of an object is simply the
contents of the string. For other classes, the description of an
object might be a human-readable form of its class name, the
object’s location in memory, and some instance
variables. You can control how instances of a class will display in
an NSLog( )
format by overriding the
class’s description method. This method returns an
NSString object.
Get Building Cocoa Applications: A Step by Step Guide 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.