An Introduction to Objective-C

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

#import

#import <Cocoa/Cocoa.h>

Includes a file if it has not been included before. Similar to #include.

id

id anObject;

Pointer to an object.

[ ]

[anObject aMethod];

Messaging operator; sends a message to an object.

self

[self display];

Pointer to the current object.

super

[super display];

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.

@interface

@interface MyClass: NSObject

Marks the beginning of a class declaration. Usually appears in a .h file.

@implementation

@implementation MyClass

Marks the beginning of a class implementation. Usually appears in a .m file.

@protocol

@protocol DrawProtocol

Marks the beginning of a protocol declaration. Usually appears in a .h file.

@class

@class NSString, NSDictionary;

Tells the compiler that a class will be referenced before it is defined. Similar to declaring a struct name * in ANSI C.

@end

@end

Notes the end of an @interface, @implementation, or @protocol section.

+

+(id)alloc;

Introduces a class method in an @interface or @implementation.

-

-(id)init;

Introduces an instance method in an @interface or @implementation.

@""

@"a string"

Used to create an unnamed NSString object. Equivalent to [NSString stringWithCString:"a string"].

Objects and Classes

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.

An application with two on-screen windows

Figure 4-2. An application with two on-screen windows

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.

Methods and Messaging

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.

The id Data Type

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

void *

id

Sample declaration

void *ptr;

id obj;

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.

A Simple Class Example

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.

Creating and Initializing Objects

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.

The @interface Directive

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.

The #import Directive

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>

Destroying Objects

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( ) or calloc( ) is freed with the free( ) function. C++ uses new to create new objects and delete 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.

The @implementation Directive

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.

The +alloc Method and the NSObject Root Class

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

NSString, NSMutableString, and NSLog

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 an NSString *.

  • 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>

autorelease and the NSAutoreleasePool Class

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.