Search the Catalog
Learning Cocoa

Learning Cocoa

By Apple Computer, Inc.
May 2001
0-596-00160-6, Order Number: 1606
370 pages, $34.95

Chapter 6
Essential Cocoa Paradigms

Contents:

Cocoa's Collection Classes
Create Graphical User Interfacesin Cocoa
Controls, Cells, and Formatters
Target/Action
Object Ownership, Retention,and Disposal

In this chapter you will explore a handful of programming topics that represent the fundamental building blocks of a Cocoa application. First, you'll learn how to use Cocoa's collection classes, a set of objects whose purpose is to hold other objects. Next, you'll explore the basics of building graphical user interfaces in Cocoa. You'll learn to use Interface Builder and discover how this powerful tool interacts with Project Builder and the Cocoa frameworks to allow you to very quickly prototype and build applications. Last, you'll delve into the intricacies of object ownership and disposal in Cocoa, ensuring that your applications use resources efficiently.

These topics have been singled out for special attention because they are central to the design of the Cocoa frameworks, and the architectural perspectives involved are likely to be unfamiliar to developers new to the subtleties of object-oriented programming. Exploring these design patterns will help steep you in the mindset of Cocoa's creators, as well as illustrate some of the powerful design approaches available with a language as dynamic as Objective-C. Understanding the principles involved will help you use the Cocoa tools and frameworks more effectively, providing the experience you need to work with them instead of against them.

Cocoa's Collection Classes

Several classes in Cocoa's Foundation framework create objects whose purpose is to hold other objects (literally, references to objects); these classes are called collection classes. The two most commonly used collection classes are NSArray and NSDictionary. NSArray stores and retrieves objects in an ordered fashion through zero-based indexing. Dictionaries store and retrieve objects using key-value pairs. These collection classes, shown in Figure 6-1, are extremely useful in Cocoa application development.

Figure 6.1. Cocoa collection classes

Collection classes come in two forms, mutable and immutable. The immutable versions of these classes (NSArray and NSDictionary, for example) allow you to add items when the collection is created but no further changes are allowed. The mutable versions (NSMutableArray, NSMutableDictionary) allow you to add and remove objects programmatically after the collection object is created.

All collection objects enable you to access a contained value that satisfies a particular external property. This property, generally referred to as a key, varies according to the organizing scheme enforced by the type of collection. For example, the key for an array is an integer that specifies position within the collection; however, a dictionary--for which the term key has more of a conventional meaning--permits any arbitrary value to act as the key for retrieving a contained value.

In addition to storage and retrieval using keys, much of the power of collection classes lies in their ability to manipulate the objects they contain. The collection classes implement methods for performing a variety of functions on the objects they contain. Not every collection object can perform every function, but in general, collection objects can:

Because you will use collection objects frequently when building Cocoa applications, it's important to get a basic feel for how they work. Exploring the collection classes will also provide an opportunity to exercise your Objective-C and Project Builder skills.

Working with NSArray

In this section you will modify the Hello World application from Chapter 5, "Hello World", so that it instantiates and exercises an instance of the NSMutableArray class:

  1. Open the Hello World project if it is not already open.

  2. Open main.m and add a declaration for an NSMutableArray just after the declaration of the autorelease pool and before the call to NSLog:

    NSMutableArray *myArray;
  3. Add the code to create a new array:

    myArray = [[NSMutableArray alloc] init];
  4. Add code to print the contents of the array object. In this call to NSLog, the %@ is replaced by the object. The %@ works exactly as it would in a printf call; %@ is the escape for an object value, in this case, an NSString. Note that NSLog automatically sends the description message to the array so that information about the array's contents will be printed:

    NSLog(@"Array description: %@ items.\n", myArray);
  5. Add code to release the array. This statement tells the runtime that we are finished with the array so it can be safely deallocated:

    [myArray release];

Your main function should look exactly like that in Example 6-1.

Example 6.1. Creating a Mutable Array

int main (int argc, const char *argv[]) 
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSMutableArray *myArray;

    NSLog(@"Hello World: This is a Foundation Tool.\n");

    myArray = [[NSMutableArray alloc] init];

    NSLog(@"Array description: %@ items.\n", myArray);

    [myArray release];
    [pool release];
    return 0;
}

Build and run the application. You should see a new string in the Run pane similar to this:

Nov 10 15:56:12 HelloWorld[256] Array description: () items.

Right now, the array is empty, but when you add items, their descriptions will appear between the parentheses.

Spend a few minutes playing with NSMutableArray. Here are some suggestions:

Consult the class specifications for NSArray and NSMutableArray in /Developer/Documentation/Cocoa for comprehensive documentation on all of the methods for this container class.

Working with NSDictionary

A dictionary--an object of the NSDictionary or NSMutableDictionary class--is a hashing-based collection whose keys for accessing its values are arbitrary, program-defined pieces of data. Although the key is usually a string (an NSString object), it can be any object--with certain caveats: the object used as a key must conform to the NSCopying protocol and respond to the isEqual: message). The keys of dictionaries are unlike the keys of the other collection objects in that, conceptually, they are also contained by the collection along with the values. Dictionaries are primarily useful for holding and organizing data that can be labeled, such as values extracted from text fields in the user interface.

In Cocoa, a dictionary differs from an array in that the key used to access a particular value in the dictionary remains the same as values are added to or removed from the dictionary--until a value associated with a particular key is replaced or removed. In an array, the key (the index) that is used to retrieve a particular value can change over time as values are inserted into or deleted from the array. Also, unlike an array, a dictionary does not put its values in any order. To enable later retrieval of a value, the key of the key-value pair should be constant (or be treated as constant); if the key changes after being used to put a value in the dictionary, the value might not be retrievable. The keys of a dictionary form a set; in other words, keys are guaranteed to be unique in a dictionary.

Working with dictionaries

In this section you will again modify the Hello World application so that it instantiates and exercises an instance of the NSMutableDictionary class:

  1. Open the Hello World project if it is not already open, and bring up main.m for editing.

  2. Add a declaration for a local variable of type NSMutableDictionary:

    NSMutableDictionary *myDict;
  3. Add the code to create a new dictionary:

    myDict = [[NSMutableDictionary alloc] init];
  4. Add code to print the contents of the dictionary object:

    NSLog(@"Dict description: %@ items.\n", myDict);
  5. Add code to release the dictionary:

    [myDict release];

Spend a few minutes playing with NSMutableDictionary. Here are some suggestions:

Consult the class specifications for NSDictionary and NSMutableDictionary for comprehensive documentation on all methods for this container class.

Create Graphical User Interfacesin Cocoa

In this section you'll see how Cocoa and Interface Builder combine to simplify and accelerate the process of constructing applications with a graphical user interface. You'll learn about:

Finally, you'll apply what you've learned by creating a Cocoa application that displays a window with a single text field containing the current date and time.

Windows in Cocoa

A window in Cocoa looks very similar to windows in other user environments such as Microsoft Windows or Mac OS 9. A window can be moved around the screen, and windows can be stacked on top of one another like pieces of paper. A typical Cocoa window has a titlebar, a content area, and several control objects.

NSWindow and the window server

Many user interface objects other than the standard window are windows. Menus, pop-up menus, and scrolling lists are primarily windows, as are dialog boxes, alerts, Info windows, and tool palettes, to name a few. In fact, anything drawn on the screen must appear in a window. End users, however, may not recognize or refer to them as "windows."

Two interacting systems create and manage Cocoa windows. On the one hand, a window is created by the window server. The window server is a process that uses the internal window management portion of Quartz (the low-level drawing system) to draw, resize, hide, and move windows using Quartz graphics primitives. As depicted in Figure 6-2, the window server also detects user events (such as mouse clicks) and forwards them to applications.

Figure 6.2. Cocoa and the window server

The window that the window server creates is paired with an object supplied by the Application Kit--an instance of the NSWindow class. Each physical window in a Cocoa program is managed by an instance of NSWindow (or subclass). When you create an NSWindow object, the window server creates the physical window that the NSWindow object manages. The window server references the window by its window number, and the NSWindow by its own identifier (see Figure 6-3.)

Figure 6.3. NSWindow objects and window server windows

Application, window, and view

In a running Cocoa application, NSWindow objects occupy a middle position between an instance of NSApplication and the views of the application. (A view is an object that can draw itself and detect user events.) The NSApplication object keeps a list of its windows and tracks the current status of each. Each NSWindow, on the other hand, manages a hierarchy of views in addition to its window, as shown in Figure 6-4.

Figure 6.4. The view hierarchy

At the top of this hierarchy is the Content view, which fits just within the window's content rectangle. The Content view encloses all other views (its subviews), which come below it in the hierarchy. The NSWindow distributes events to views in the hierarchy and regulates coordinate transformations among them.

Another rectangle, the frame rectangle, defines the outer boundary of the window and includes the titlebar and the window's controls. Cocoa uses the lower-left corner of the frame rectangle to define the window's location relative to the screen's coordinate system and to establish the base coordinate system for the views of the window. This is different from Carbon and Classic applications, which use the upper-left corner as the origin of the coordinate system. Views draw themselves in coordinate systems transformed from (and relative to) this base coordinate system.

Key and main windows

Windows have numerous characteristics. They can be onscreen or offscreen. Onscreen windows are layered on the screen in tiers managed by the window server. Onscreen windows also can carry a status: key or main.

The key window responds to key presses for an application and are the primary recipient of messages from menus and dialog boxes. Usually a window is made key when the user clicks it. Each application can have only one key window at a time.

An application has one main window at any given time, which can often have key status as well. The main window is the principal focus of user actions for an application. Often user actions in a modal key window (typically a dialog box such as the Font dialog box or an Info window) have a direct effect on the main window.

Nib Files

A nib file is an archive of object instances generated by Interface Builder. Unlike the product of many user interface building systems, a nib file is not generated code; it is a true object that has been specially encoded and stored on disk. The objects in the nib file are created and manipulated using Interface Builder's graphical tools.

Nib files typically package a group of related user interface objects and supporting resources, along with information about how the objects are related--both to one another and to other objects in your application. Every application with a graphical user interface has at least one nib file that is loaded automatically when the application is launched. The main nib file typically contains the application menu, while auxiliary nib files contain the application windows with their associated user interface objects. An important advantage of splitting an application's interface into several nib files is that portions of the user interface may be loaded only when needed.

A nib file contains one or more of the following:

Outlets

An outlet is an instance variable that contains a reference to another object. An object can communicate with other objects in an application by sending messages to them through outlets, as shown in Figure 6-5.

Figure 6.5. Using an outlet to send a message

An outlet can reference any object in an application: user interface objects such as text fields and buttons, windows and dialog boxes, instances of custom classes, and even the application object itself. What distinguishes outlets from other instance variables is their relationship to Interface Builder.

Interface Builder can "recognize" an outlet declaration in a header file, allowing you to set its value by drawing connection lines between objects. Specifying these relationships between objects in Interface Builder saves you from having to writeinitialization code by hand. There are ways other than outlets to reference objects in an application, but outlets and Interface Builder's facility for initializing them are a great convenience.

Create a Cocoa Application Project

In this section you'll use Interface Builder and Project Builder to create a simple Cocoa application. When it is run, the application displays a single window containing a text field and uses an outlet to display the current date and time in that text field.

  1. If Project Builder is not already running, launch it.

  2. Choose the New Project command. Project Builder will display the New Project Assistant, as shown in Figure 6-6.

    Figure 6.6. New Project Assistant

  3. Select Cocoa Application from the list of project templates and click Next. Much like the Foundation Tool template you used in the Hello World example, the Cocoa Application template provides a starting point for applications with a GUI. It includes a default nib file containing a window and standard menu bar.

  4. Type Nib Files in the Project Name field and click Finish.

Notice that Project Builder uses hierarchical groups to organize a project. The default groups for a Cocoa application are:

These groups are very flexible in that they do not necessarily reflect either the on-disk layout of the project or the manner in which the build system handles the files. The default groups created for you by the templates can be used as they are, or rearranged however you like.

Open the Main Nib File

In order to begin constructing a user interface, you must open the application's main nib file in Interface Builder.

  1. In the Finder, locate and open the Nib Files.pbproj that you created earlier.

  2. Double-click MainMenu.nib in the Resources group of the Groups & Files list of Project Builder's main window. This will launch Interface Builder (if it is not already running) and open the nib file.

A default menu bar and window titled Window will appear when Interface Builder opens the nib file, as shown in Figure 6-7.

Figure 6.7. A new Cocoa application's default menu bar and nib file

Move and Resize the Window

Interface Builder stores all kinds of information about user interface objects in nib files. For example, you can set both the size and initial location of an application's main window by simply resizing and moving the window in Interface Builder:

  1. Move the window near the upper-left corner of the screen by dragging its titlebar.

  2. Make the window smaller using the resize control at the bottom-right corner of the window, as shown in Figure 6-8.

    Figure 6.8. Cocoa window with resize control

Add a Text Field

Now add a text field object to the application's window.

  1. Select the Views palette by clicking the second button from the left at the top of the Cocoa objects window. If you don't see the Cocoa palette window (Figure 6-9), select Palettes from the Tools menu to bring it forward.

    Figure 6.9. Interface Builder's Views Palette

  2. Drag a text field object onto the window. You'll find it beneath the Button object on the left side of the Views palette.

  3. Resize the text field to make it wider by grabbing the right handle and dragging toward the right, as shown in Figure 6-10.

    Figure 6.10. Resizing a text field

Create a Custom Subclass

Object-oriented applications frequently contain one or more controller classes that are responsible for reacting to user input from the user interface objects and performing some activity (often delegating tasks to other objects). The controller class in this application will be responsible for sending the text to the text field object for display.

In order to define a new class, you must go to the Classes pane of the MainMenu.nib window. Once there, the first thing you do is choose the superclass, the class from which your new subclass will inherit.

  1. Select the Classes pane by clicking the Classes tab in the MainMenu.nib window.

  2. Your new subclass will not need to inherit any complex behavior, so select NSObject from the list of classes (it's at the very top of the list, as shown in Figure 6-11). NSObject will provide it with everything necessary to function as a Cocoa object.

    Figure 6.11. Subclassing NSObject

  3. Choose Subclass from the Classes menu.

  4. Type MyController to replace the text MyObject and press Return.

Now your class is established in the hierarchy of classes within the nib file.

Define an Outlet for the Class

MyController needs a way to send messages to the text field in the main window. Use Interface Builder to create an outlet for that purpose:

  1. Select MyController in the Classes pane of the MainMenu.nib window.

  2. Click the electrical outlet icon to the right of the class (see Figure 6-12).

    Figure 6.12. Adding an outlet

  3. Choose Add Outlet from the Classes menu.

  4. Name this outlet textField and press Return.

Generate an Instance of the Class

As the final step of defining a class in Interface Builder, create an instance of your class and connect it to other objects in the nib file:

  1. Select MyController in the Classes pane of the MainMenu.nib window.

  2. Choose Instantiate from the Classes menu.

When you instantiate a class (that is, create an instance of it), Interface Builder switches to the Instances pane (shown in Figure 6-13) and highlights the new instance, which is named after the class.

Figure 6.13. Interface Builder's Instances pane

In fact, the Instantiate command does not generate a true instance of MyController. It creates a proxy object used within Interface Builder for defining connections to other objects in the nib file. When the application is launched and the nib file's contents are unarchived, the runtime system creates a true instance of MyController and uses the proxy object to establish connections to other objects in the nib file.

Connect the Custom Class to the Interface

Now that you have created a proxy instance of MyController, you can use it to declare a connection between it and the text field you created earlier:

  1. In the Instances pane of the MainMenu.nib window, Control-drag a connection line from the MyController instance to the text field. When the text field is outlined (Figure 6-14), release the mouse button.

    Figure 6.14. Connecting the instance to the text field

  2. Interface Builder brings up the Connections pane of the Custom Object Info window shown in Figure 6-15.

    Figure 6.15. Custom Object Info window

  3. Click the Connect button.

  4. Save the nib file (Command-S).

Now that you have created this connection, when the nib file is loaded at application launch time, the runtime system will know that you want MyController's textField outlet (remember, an outlet is just an instance variable) to be initialized to refer to the text field object in the window.

Generate the Source Files

Interface Builder generates source code files from the (partial) class definitions you've made. These files are skeletal, in the sense that they contain little more than essential Objective-C directives and the class definition information. You'll usually need to supplement these files with your own code. Note that, unlike some interface construction tools, the files generated by Interface Builder do not contain the code to create the interface you laid out; all of that information is stored in object archives in the nib file.

  1. Go to the Classes pane of the nib file window.

  2. Select the MyController class.

  3. Choose Create Files from the Classes menu.

Interface Builder then displays the dialog box shown in Figure 6-16.

Figure 6.16. The Create Files dialog box

  1. Verify that the checkboxes in the Create column next to the .h and .m files are selected.

  2. Verify that the checkbox next to Nib Files is selected.

  3. Click the Choose button.

Now we leave Interface Builder for the Nib Files application. You'll complete the application using Project Builder.

Examine a Header File in Project Builder

When Interface Builder adds the source files to the Nib Files project, Project Builder does its best to figure out which group the new files belong in by examining the existing groups. Project Builder will add the source files to the group that contains other source files or at the top level, if no other source files are in the project. Project Builder's groups are simply arbitrary organizational containers, so you can arrange files however you wish. However, because these files are the interface and implementation for a class, it makes sense to put them in the Classes group.

  1. Activate Project Builder's main window.

  2. Drag MyController.h and MyController.m into the Classes group.

Clicking a file's name in the list will cause its contents to be displayed in the code editor on the right side of Project Builder's main window.

Statically Type the Outlet

By default, outlet declarations are dynamically typed using the id keyword. You can use id as the type for any object, meaning that the class of the object is determined at runtime. When you don't need a dynamically typed object, you can--and should--statically type it as a pointer to an object. It takes a little extra time, but it is good programming practice. Static typing also allows the compiler to perform type checking, potentially saving you debugging time later.

Generic outlets are declared as:

IBOutlet id variableName;

Change the declaration in:

MyController.h

to read:

IBOutlet NSTextField *textField;

Implement MyController's awakeFromNib Method

When an application is launched, the NSApplicationMain function loads the main nib file. After a nib file has been completely unpacked and its objects connected, the runtime system sends the awakeFromNib message to all objects that were derived from information in the nib file, signaling that the loading process is complete. All object's outlets are guaranteed to be initialized when awakeFromNib is called. This gives the objects in the nib file an opportunity to do any extra setup that they require before the user or the rest of the application attempts to interact with them.

In this example, you will use MyController's awakeFromNib method to print a message to the text field in the application's main window.

Declare the awakeFromNib method in MyController.h:

  1. Select MyController.h from the Classes group in the Groups & Files list in Project Builder's main window.

  2. Insert the declaration between the instance variable declarations and the @end statement:

    @interface MyController : NSObject
    {
        IBOutlet NSTextField *textField;
    }
    - (void)awakeFromNib;
    @end

Method implementations go in MyController.m, between @implementation <class name> and @end. This is where you will add the code for MyController's awakeFromNib method:

  1. Select MyController.m from the Classes group in Project Builder's main window.

  2. Insert the code for awakeFromNib:

    @implementation MyController
    - (void)awakeFromNib
    {
         [textField setObjectValue:[NSCalendarDate date]];
    }
    @end

The awakeFromNib method implementation simply sends the current date and time to the text field for display.

Build, Debug, and Run Nib Files

By clicking the Build button in Project Builder, you invoke the build process, coordinating the compilation and linking procedure that results in an executable file. It also performs other tasks needed to build an application.

The build process invokes the compiler, passing it the source code files of the project. Compilation of these files (Objective-C, C++, and standard C) produces machine-readable object files for the architecture or architectures specified for the build.

In the linking phase of the build, the build tool executes the linker, passing it the libraries and frameworks to link against the object files. Frameworks and libraries contain precompiled code that can be used by any application. Linking integrates the code in libraries, frameworks, and object files to produce the application executable file.

During the build process, nib files, sound, images, and other resources are copied from the project to the appropriate localized or nonlocalized locations in the application package. An application package is a directory that contains the application executable and the resources needed by that executable. This directory appears as a single file in the Finder that can be double-clicked to launch the application.

Build the project

When you click the Build button, the build process begins. When Project Builder finishes--and encounters no errors along the way--it displays Build Succeeded in the lower-left corner of the main window.

  1. Save source code files and any changes to the project.

  2. Click the Build button in the main window.

Debug the project

Of course, rare is the project that is flawless from the start. Project Builder is likely to catch some errors when you first build your project. To see the error-locating features of Project Builder, introduce a trivial mistake into the code:

  1. Delete a semicolon in the code, creating an error.

  2. Click the Build button in the main window.

  3. Click the error-notification line that appears in the build error browser.

  4. Fix the error.

  5. Rebuild the project.

One aspect of developing and debugging Cocoa applications that differs substantially from other environments is the role Interface Builder plays in the process. Because so much of the runtime behavior of applications is determined by connections you create in Interface Builder, you must remember to check not only your code for problems, but the nib files as well.

For example, if you run the Nib Files application and nothing happens, it could be that there is nothing at all wrong with your code; it may be that you just didn't connect the objects properly in Interface Builder. So remember, when something goes wrong, first think about the nature of the problem and decide if it makes sense to look in Interface Builder or at your code for the source of the problem.

Run the application

Click the Run button in Project Builder to launch the application.

If you want, you can locate the application in the Finder (in the build subdirectory of the Nib Files project, unless you changed the default location), double-click it, and try it out. In either case, you should see something very similar to Figure 6-17.

Figure 6.17. Nib files final output

Controls, Cells, and Formatters

Controls and cells lie behind the appearance and behavior of most user interface objects in Cocoa, including buttons, text fields, sliders, and browsers. Although they are quite different types of objects, they interact closely.

Controls enable users to signal their intentions to an application and thus control what is happening. Cells are rectangular areas embedded within a control. Some controls can hold multiple cells as a way to partition their surfaces into active areas. Cells can draw their own contents either as text or image (and sometimes as both), and they can respond individually to user actions. Figure 6-18 shows the relationship between controls and cells.

Figure 6.18. Controls and cells

Controls act as managers of their cells, telling them when and where to draw and notifying them when a user event (mouse click or keystroke) occurs in their areas. This division of labor, given the relative "weight" of cells and controls, conserves memory and provides a great boost to application performance. For example, a matrix of buttons can be implemented as a single control with many cells, instead of a set of individual controls.

A control does not have to have a cell associated with it, but most user interface objects available on Interface Builder's standard palettes are cell-control combinations. Even a simple button--from Interface Builder or programmatically created--is a control (an NSButton instance) associated with an NSButtonCell. The cells in a control such as a matrix must be the same size, but they can be of different classes. More complex controls, such as table views and browsers, can incorporate various sizes and types of cells. Most controls that use a single cell, such as NSButton, provide convenience methods so you don't usually have to deal with the contained cell directly.

Cells and Formatters

When one thinks of the contents of cells, it's natural to consider only text (NSString) and images (NSImage). The content seems to be whatever is displayed. However, cells can hold other kinds of objects, such as dates (NSDate), numbers (NSNumber), and custom objects (say, phone number objects).

One way to make your application's user interface more attractive is to format the contents of fields that display currencies and other numeric data. Fields can have fixed decimal digits, limit numbers to specific ranges, have currency symbols, and show negative values in a special color.

Formatters are objects that translate the values of certain objects to specific onscreen representations; formatters also convert a formatted string on a user interface into the represented object. For example, Figure 6-19 shows how a date formatter translates the contents of an NSDate object into a specific string for display.

Figure 6.19. A date formatter

You can create, set, and modify formatter objects programmatically or by using Interface Builder. And you can create your own special formatter objects (that format phone numbers, for example) and add them to your own palette.

Formatter objects handle the textual representation of the objects associated with cells and translate what is typed into a cell into the underlying object. You can attach a formatter object to a cell in Interface Builder or use NSCell's setFormatter: method to programmatically associate a formatter with a cell.

A Formatted Cell Example

In this example you will modify the project you created in the previous section (nib files) so that the text cell formats the date before display. Interface Builder provides two formatter objects on its standard palettes, one for formatting dates and the other for formatting numbers. You'll use the first of these.

  1. Open the Nib Files project.

  2. Double click the MainMenu.nib file to open it in Interface Builder.

  3. Select the Data Views Palette in the palette window.

  4. Drag a date formatter object to the text field, as shown in Figure 6-20.

    Figure 6.20. Adding a date formatter to a text field

  5. While the text field is selected, bring up the Info window (Command-Shift-I) if it is not already visible.

  6. In the Formatter pane of the Info window, specify a date format by selecting the row with the %c format as shown in Figure 6-21.

    Figure 6.21. Configuring a date formatter

  7. Save the nib file.

  8. Build and run the project. You should see something very much like Figure 6-22.

    Figure 6.22. Nib files application using a date formatter

Target/Action

The target/action pattern is part of the mechanism by which user interface controls respond to user actions, enabling users to communicate their intentions to an application. The target/action pattern specifies a one-to-one relationship between two objects; the control (more specifically, the control's cell) and its target. When a user clicks a user interface control, the control sends an action message to the target object as depicted in Figure 6-23.

Figure 6.23. Target/action

The target/action relationship is typically defined using Interface Builder, in which you select a target object for a control, along with the specific action message that will be sent to the target. Target/action relationships can also be set (or modified) while an application is running.

Target/Action Example

The following steps take you through the process of building a simple example application that uses the target/action pattern. In this example, clicking a button in the main window causes the date and time to be updated in a text field.

Create the project and user interface

Since the first example provided detailed instructions on creating projects using Project Builder, this example generally does not repeat those instructions. If you forget how to do a step, refer back to the Nib Files project in "Create Graphical User Interfacesin Cocoa" to refresh your recollection.

  1. Start Project Builder, if it is not already running.

  2. Create a new Cocoa application project called TargetAction.

  3. Open the main nib file.

  4. Drag a text field onto the main window and make it about four times the default width.

  5. Drag a button onto the main window

  6. Double-click the button and rename the button by typing Refresh.

When you're finished, you should have a window that looks something like Figure 6-24.

Figure 6.24. Target/action application interface

Create the controller class

Here you'll subclass NSObject to create a controller class for the application.

  1. Click the Classes tab of the MainMenu.nib window.

  2. Select NSObject from the list of classes.

  3. Press Return to create a new subclass and type MyController to replace the text MyObject.

Define the outlets of the class

The controller class needs an outlet for the text field so it can send messages to the text field.

  1. Select MyController in the Classes pane.

  2. Click the electrical outlet icon to the right of the class.

  3. Press Return to create a new outlet.

  4. Name this outlet textField and press Return.

Define an action for the class

You will define one action method for MyController called refresh:. When the user clicks the Refresh button, a refresh: message is sent to the target object, an instance of MyController. Action refers both to a message sent to an object when the user manipulates a control object and to the method that is invoked.

  1. Click Actions under MyConverter in the Classes pane shown in Figure 6-25.

    Figure 6.25. Defining an action

  2. Press Return to create a new action, and type the name of the method, refresh. IB adds the : for you.

Generate an instance of the class

Now you must create a proxy instance of the controller class so you can make connections between the controller object and UI objects.

  1. Click MyController in the Classes list to select it.

  2. Choose Instantiate from the Classes menu. The instance will appear in the Instances pane.

Connect the interface controls to the class's actions

For MyController to receive an action message from the button in the user interface, you must connect the button to MyController. The button object keeps a reference to its target using an outlet--not surprisingly, the outlet is named target.

You can view (and complete) target/action connections in the Connections Info window in Interface Builder. The upper-right column of the Connections Info window lists all action methods defined by the class of the target object and known by Interface Builder.

  1. Control-drag a connection from the Refresh button to the MyController instance in the MainMenu.nib window. When the instance is outlined, release the mouse button, as shown in Figure 6-26.

    Figure 6.26. Connecting a button to an object instance

  2. In the Connections pane, make sure target in the Outlets column is selected.

  3. Select refresh: in the column on the right, as shown in Figure 6-27.

    Figure 6.27. Connecting the target to its action

  4. Click the Connect button.

  5. Save the main nib file.

Developers new to Cocoa sometimes get confused when making action and outlet connections in Interface Builder. In general, you need only follow a simple rule to know which way to draw a connection line. Draw the connection in the direction that messages will flow:

The difference between target/action and outlet connections is depicted in Figure 6-28.

Figure 6.28. Target/action and outlet connections

Another way to clarify connections is to consider who needs to find whom. With outlets, the custom object needs to find some other object, so the connection is from the custom object to the other object. With actions, the control object needs to find the custom object, so the connection is from the control object.

Connect the custom class to the interface

So that you can tell when the refresh: action method is invoked, the application must provide some kind of feedback. For this example, clicking the Refresh button will print the current date and time.

Connect MyController to the text field in the main window as you did in "Create Graphical User Interfacesin Cocoa". The refresh: method will use this outlet to send text to the text field.

  1. In the Instances pane of the nib file window, Control-drag a connection line from the MyController instance to the text field. When the instance is outlined, release the mouse button.

  2. Interface Builder brings up the Connections pane of the Info window. Select the outlet textField.

  3. Click the Connect button.

Generate the source files

At this point you've finished declaring MyController's outlets, actions, and connections, so it's time to create the source files for the class and add them to the Project Builder project.

  1. Go to the Classes pane of the nib file window.

  2. Select the MyController class.

  3. Choose Create Files in the Classes menu.

  4. Verify that the checkboxes in the Create column next to the .h and .m files are selected.

  5. Verify that the checkbox next to the TargetAction target is selected.

  6. Click the Choose button.

  7. Save the nib file.

Now we leave Interface Builder for this application. You'll complete the application using Project Builder.

Implement MyController's action method

Fill in the implementation of MyController's refresh: method.

  1. In Project Builder, move MyController.h and MyController.m into the Classes group.

  2. Select MyController.m and insert the code for refresh: as shown:

    @implementation MyController
    - (IBAction)refresh:(id)sender
    {
         [textField setObjectValue:[NSCalendarDate date]];
    }
    @end

The method simply sends the current date and time to the text field so it is updated every time you click the Refresh button.

Build and debug the application

To smooth the task of debugging, Project Builder puts a graphical user interface over the GNU debugger, GDB. To help familiarize you with the debugger, use it now to set a breakpoint on the refresh: method.

  1. Click MyController.m in Project Builder's Groups & Files list.

  2. Locate the refresh: method in the Text pane.

  3. Set a breakpoint by clicking in the column to the left of the code listing. See Figure 6-29

    Figure 6.29. Setting a breakpoint

  4. Click on the Build button to build the project.

  5. Run the debugger by clicking the Debug button, or press Command-R.

  6. When the application launches, click the Refresh button in its window.

  7. When you click Refresh, the debugger will activate, stop at the breakpoint, and allow you to examine the call stack and the value of local variables.

To perform complex debugging tasks, you can use the GDB console. Click the Console tab at the top righthand corner of the window to expose the Console. When you click in the Console window and press Return, the (gdb) prompt appears. There are many GDB commands that you can type at this prompt that are not represented in the user interface. For online information on these commands, enter help at the prompt.

Object Ownership, Retention, and Disposal

The problem of object ownership and disposal is a natural concern in object-oriented programming. When an object is created and passed around among various "consumer" objects in an application, which object is responsible for disposing of it? And when? If the object is not deallocated when it is no longer needed, memory leaks. If the object is deallocated too soon, problems may occur in other objects that assume its existence, and the application may crash.

The Foundation framework introduces a mechanism and a policy that helps to ensure that objects are deallocated when--and only when--they are no longer needed.

The policy is quite simple: you are responsible for disposing of all objects that you own. You own objects that you create, either by allocating or copying them. You also own (or share ownership in) objects that you retain. The flip side of this rule is that you should never release an object that you have not retained or created.

Object Initialization and Deallocation

In Cocoa you usually create an object by allocating it (alloc) and then initializing it (init or a variant). For example:

NSArray *myArray = [[NSArray alloc] init];

When an array's init method is invoked, the method implementation initializes its instance variables to default values and completes other startup tasks. Similarly, when an object is deallocated, its dealloc method is invoked, giving it the opportunity to release objects it has created, free allocated memory, and so on.

But now another question arises. If the owner of an object must release the object within its programmatic scope, how can it give that object to other objects? The short answer is: the autorelease method, which marks the receiver for later release, enabling it to live beyond the scope of the owning object so that other objects can use it.

The autorelease method must be understood in a larger context of the autorelease mechanism for object deallocation. Through this programmatic mechanism, you implement the policy of object ownership and disposal.

Reference Counting

Each object in Cocoa has an associated reference count. When you allocate or copy an object, its reference count is set at 1. You send release to an object to decrement its reference count. When the reference count reaches 0, NSObject invokes the object's dealloc method, after which the object is destroyed. However, successive consumers of the object can delay its destruction by sending it retain, which increments the reference count. You retain objects to ensure that they won't be deallocated until you're done with them.

Autorelease Pools

Each application puts in place at least one autorelease pool (for the event cycle) and can have many more. An autorelease pool tracks objects marked for eventual release and releases them at the appropriate time. You put an object in the pool by sending the object an autorelease message. In the case of an application's event cycle, when code finishes executing and control returns to the application object (typically at the end of the cycle), the application object sends release to the autorelease pool, and the pool releases each object it contains. If afterward the reference count of an object in the pool is 0, the object is deallocated. Figure 6-30 illustrates the process.

Figure 6.30. The life of an object in an autorelease pool

  1. myObj creates an object:

    anObj = [[MyClass alloc] init];

  2. myObj returns the object to yourObj, autoreleased. This puts the object in the autorelease pool; that is, the autorelease pool starts tracking the object:

    return [anObj autorelease];

  3. yourObj retains the object. The retain message increments the reference count to 2 (both myObj and yourObj now have references to anObj):

    [anObj retain];

  4. At the end of the event cycle, the autorelease pool sends release to all of its objects, thereby decrementing their reference counts. Objects with reference counts of 0 are deallocated. Since anObj previously had a reference count of 2, when the autorelease pool sends a release message to it, anObj still has a reference count of 1, so it is not deallocated.

  5. When yourObj no longer needs anObj, it sends autorelease to anObj, putting it into an autorelease pool again. At the end of the event cycle, the autorelease pool once again sends release to its objects; since the reference count of anObj is now 0, it's deallocated.

When an object is used solely within the scope of the method that creates it, you can deallocate it immediately by sending it release. Otherwise, send autorelease to all objects you have created that you no longer need but will return or pass to other objects.

You shouldn't release objects that you receive from other objects (unless you precede the release or autorelease with a retain). You don't own these objects and can assume that their owner is responsible for their eventual deallocation. You can also assume that a received object remains valid within the method it was received in. That method can also safely return the object to its invoker.

You should send release or autorelease to an object only as many times as are allowed by its creation (one) plus the number of retain messages you have sent it. You should never send free or dealloc to a Cocoa object (with one exception: when you are overriding a class's dealloc method).

Implications of Retained Objects

When you retain an object, you're sharing it with its owner and other objects that have retained it. While this might be what you want, it can lead to some undesirable consequences. If you had retained an object's instance variable (returned from an accessor method) and the owner is released, the object referred to by the instance may well be invalid. If you had retained an instance variable of the owning object, and that instance variable refers to a mutable object that is later modified, your code could be referencing something it does not expect.

Copying Versus Retaining

When deciding whether to retain or copy objects, it helps to categorize them as value objects or entity objects. Value objects are objects such as NSNumbers or NSStrings that encapsulate a discrete, limited set of data. Entity objects, such as NSViews and NSWindows, tend to be larger objects that manage and coordinate subordinate objects. For value objects, use copy when you want your own snapshot of the object (the object must conform to the NSCopying protocol); use retain when you intend to share the object. Always retain entity objects.

For more information on the subtleties of copying objects in Cocoa, see the reference documentation for the NSCopying protocol.

Reference Counting in Accessors

Accessor methods are used to get and set an object's instance variables. The declaration for an accessor method that returns a value is, by convention, the name of the instance variable preceded by the type of the returned value in parentheses. An accessor method that sets the value of an instance variable begins with set, followed by the name of the instance variable (initial letter capitalized). The set method's argument takes the type of the instance variable and the method itself returns void.

If you don't want to allow an instance variable's value to be changed by any object other than one of your class, don't provide a set method for the instance variable. If you do provide a set method, make sure objects of your own class use it when specifying a value for the instance variables. This has important implications for subclasses of your class.

In accessor methods that set value-object instance variables, you usually (but not always) want to make your own copy of the object and not share it. (Otherwise it might change without your knowledge.) Send autorelease to the old object and then send copy--not retain--to the new one, as demonstrated in Example 6-2.

Example 6.2. A setTitle: Method

- (void)setTitle:(NSString *)newTitle
{
    [title autorelease];
    title = [newTitle copy];
}

Deciding how to implement accessor methods, especially setter methods, can be tricky. There are many issues you must keep in mind, some more obvious than others. The remainder of this section discusses the most critical of these issues.

When to release

In Example 6-2, the reason title needs to be released before it is set to something else is because title is a pointer to an NSString object. So if title is set to newTitle without first autoreleasing it, then the original NSString that title pointed to will be leaked; once title is pointing to the new NSString, there is no way to release the previous value of title.

When to copy

It is often better to copy leaf value objects like NSStrings instead of just retaining them. Consider the following example:

NSMutableString *foo = [NSMutableString stringWithCString:"foo"];
[myWindow setTitle:foo];
[foo appendString:@"bar"];

If NSWindow retained (instead of copied) the string passed to it in setTitle:, the title of the window would now be foobar, instead of foo, which is probably not the intended result. Note that copy will actually just increment the reference countof an object (an implicit retain) if it can do so safely--if, for instance, the receiver is actually an immutable NSString.

Release or autorelease

Another issue is whether to release or autorelease an instance variable before changing it. If in Example 6-2title is released instead of autoreleased, it immediately becomes invalid. So if newTitle pointed to the same object as title, the title would now point to garbage because you released what you are about to set.

Consider another possible predicament for the object invoking such a setTitle: method:

{
    /* ... */
   title = [myWindow title];
   [myWindow setTitle:newTitle];
    /*...*/
    // title is now garbage because it was released by setTitle:
 }

The code following the setTitle: invocation will fail if it expects title to be a valid NSString.

A helpful rule of thumb is that Foundation objects (NSArray and NSDictionary, for example) never autorelease; they release. This is to maximize performance. However, at the Application Kit level, and in user interface classes especially, autorelease is used to maintain the general Application Kit API promise of autoreleased returns: the returned value is valid within the calling context until the autorelease pool is emptied.

When implementing your own classes, be aware of these trade-offs and dangers. Using release is likely the better choice in cases in which you work with objects that no client will ever get a handle to. Using release makes it much easier to track down retain/release bugs in your code. However, autorelease should be used for instance variables that clients are able to access.

Tips for Eliminating Deallocation Bugs

Problems in object deallocation are not unusual for beginning Cocoa programmers. You might release an object too many times or you might not release an object as many times as is needed to deallocate it. Both situations lead to nasty problems--in the first case, to runtime errors when your code references nonexistent objects; the second case leads to memory leaks.

Here are a couple of things to remember that might help you avoid deallocation bugs in Cocoa code:

Back to: Sample Chapter Index

Back to: Learning Cocoa


O'Reilly Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies

© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com