mac_ch6.eps
co_bar.eps

Chapter 6: Getting Started With Classes and Messages in Application Design

mac_chbox6.eps

In This Chapter

Understanding the Cocoa development process

Understanding applications

Discovering object methods and properties

Creating subclasses

Introducing Code Sense

Receiving messages from OS X with a delegate

Receiving messages from OS X with NSResponder

Although Cocoa and Xcode both use Objective-C, having a theoretical understanding of classes isn't enough to work with them effectively. Essential practical skills include:

bl.eps Understanding the structure of a Cocoa application.

bl.eps Finding the right Cocoa object to solve a problem or implement a feature.

bl.eps Translating the Cocoa documentation into working code.

bl.eps Using Xcode and Interface Builder to add, release, and manage application objects.

Understanding the Cocoa Development Process

Cocoa code often requires detective work. Cocoa objects are complex, with many features that cross-reference other objects and data types. The developer documentation is comprehensive, but the links between classes and supporting features are perhaps not as clear as they might be.

When you use a Cocoa class for the first time, it's likely you'll follow a process that I'll outline in this chapter, with false starts, incorrect guesses, and repeated searches through the documentation. When you discover a solution, it's likely to be elegant and powerful. You can often implement a sophisticated solution with a single line of code, but it may take you some time to find this ideal.

Timesaving help is always welcome, which is why it's so helpful to look for sample code and related online discussions. Occasionally you can find a worked example that solves your problem. More often you'll find references to unexplored or unnoticed features that can point you toward a solution. Generally, you'll save time by looking for worked examples outside the documentation; for example, in Apple's own sample code and solutions posted in online forums.

Understanding Applications

I'll use the minimal sample project from Chapter 4 as a teaching lab for getting started with application design. Begin by launching Xcode and reloading the project from Chapter 4 called First. You'll see three groups — Classes, Other Sources, and Resources — at the top of the Groups & Files pane in Xcode. Click the reveal triangles beside them to explore their contents, and then click main.m, as shown in Figure 6.1.

Figure 6.1

main.m is included as the launch point for every application, but it is usually left unedited. The argc and argv parameters are ignored, and the function doesn't return a useful result code.

495896-fg0601.tif

These three groups define the key elements in an application. Although only some of them are code files, you can think of them as the application's source code. The other items in the Groups & Files pane define how Xcode builds these elements into a finished application.

mac_note.eps Note

The easy, but unexpected, way to set the developer name and copyright info in the comments at the start of each file is to create a card with personal details in Address Book and choose Make This My Card from the Card menu. Xcode reads the information from Address Book when it creates a new project. You can also enter the following in Terminal on a single line:

defaults write com.apple.Xcode PBXCustomTemplateMacroDefinitions

‘{“ORGANIZATIONNAME”=”<OrgNameHere>”;}'

Table 6.1 lists the key elements in each folder. In this chapter, you'll concentrate on the contents of the top three groups. All elements are essential — a project won't build without them — but some can be used as is, without changes.

mac_note.eps Note

The files in a real project include the project name as a prefix. This prefix isn't included in the table.

Table 6.1 Elements of a Cocoa Application

Element

Explanation

Group

Class files

Source code for the application's class templates.

Classes

main.m

Standard application start-up code. (This file can be edited, but isn't usually modified.)

Other Sources

_Prefix.pch

Precompiled headers used to avoid repeat compilation of the Cocoa headers. Other common project headers can be added here.For simple projects, this file isn't changed.

Other Sources

-info.plist

Default application settings, including the names of the first loaded class and the default nib file.

Resources

InfoPlist.strings

Used for localization; a “folder” that holds a file with a list of text strings for each language supported by the application.

Resources

Nib files

At least one nib file, defined in -info.plist, is loaded automatically. Other nib files can be defined and loaded on demand, if needed.

Resources

mac_caution.eps Caution

When you add new files to a project, Xcode doesn't attempt to move them to the correct groups. The group structure is cosmetic. It doesn't affect compilation, but it does make it easier to keep related files together.

Exploring standard application elements

In an empty template, there are exactly three source code files. Two are usually used as is, and another is only modified occasionally. The main.m file, shown in Figure 6.1, is a standard C main() function. All Cocoa applications include this file. It runs a loader function called NSApplicationMain(), which initializes a memory manager, and then loads and runs the body of the application. It's possible to modify this file to create a different start-up environment, but you can ignore it in most projects.

Similarly, you can usually ignore the _Prefix.pch file. It contains an import directive for the Cocoa library header collections and is used to speed up compilation by making sure that these headers are only compiled once. Occasionally you may choose to add extra header collections here. More typically, you'll leave it unchanged.

By default, your application ignores the Info.plist option for string localization. There are two ways to support non-English languages in a Cocoa application: localization strings and localized nib files. If you're developing for an English-speaking audience, you can ignore these options. If not, you can find out more about localization in Chapter 9.

Introducing the application delegate

Active application code is collected in the files in the Classes folder. If your project is named First you'll see two files called FirstAppDelegate.h and FirstAppDelegate.m.

mac_tip.eps Tip

For clarity, the Classes folder only includes code for subclasses. You can — and often do — use Cocoa classes and objects as is, without subclassing them.

The architecture of a Cocoa application is unexpectedly indirect. An NSApplication object exists, but you rarely access it directly. Instead, whenever OS X sends an application-level message, the message is passed to an application delegate object. The delegate handles messages that are sent by OS X when the application is about to quit, when the user hides it or unhides it, when OS X runs out of memory, and so on.

Unlike the application object, the delegate is designed to be subclassed. When you create a new project, a delegate subclass with the AppDelegate suffix is created for you automatically. To a good approximation, this delegate is the application.

mac_note.eps Note

Using a delegate may seem redundant and unnecessarily complex, but it offers practical advantages. You'll look at them later in this chapter.

This delegate can be extended with handlers for every application control message generated by OS X, but the default template-generated delegate is lightweight and does very little. The code defines a link to a window object and includes a single stub method that is triggered when the application loads.

Click FirstAppDelegate.h to reveal it in the Editor Window. The interface looks like this:

#import <Cocoa/Cocoa.h>

@interface FirstAppDelegate : NSObject <NSApplicationDelegate> {

NSWindow *window;

}

@property (assign) IBOutlet NSWindow *window;

@end

Table 6.2 breaks down the features in detail.

Table 6.2 Sample Class Interface Feaures

Feature

Explanation

#import <Cocoa/Cocoa.h>

This line imports the Cocoa framework headers. The default template adds this line — and the others — “for free.” When you reference a framework in a class, you must add a corresponding line here by hand to import its headers.

@interface FirstAppDelegate

The code that follows is the interface for the FirstAppDelegate class.

: NSObject

This class is a subclass of NSObject and inherits its methods and properties.

<NSApplicationDelegate>

The class imports and uses a bundle of predefined optional methods called the NSApplicationDelegate Protocol.This particular protocol is part of Cocoa. It defines a set of optional application management methods that may be implemented in the delegate.

NSWindow *window;

The class includes an instance of Cocoa's NSWindow class, accessed via a pointer named window.

@property

window can be a public property.

(assign)

window uses assign operations for memory management.

(Memory management is introduced in Chapter 12.)

IBOutlet

window is a pointer to an object defined in an associated nib file.window isn't allocated in code; it's loaded automatically from the nib file when the application loads.The nib loading process also initializes its pointer.

FirstAppDelegate.m is shown next. Table 6.3 breaks down its features.

#import “FirstAppDelegate.h”

@implementation FirstAppDelegate

@synthesize window;

- (void) applicationDidFinishLaunching: (NSNotification *) aNotification {

//Insert code here to initialize your application

}

@end

Table 6.3 Sample Class Interface Features

Feature

Explanation

#import “FirstAppDelegate.h”

This line imports the class interface file. (If you don't include this line or forget to rename the header if you rename a class, the compiler can't find the variable and method definitions it needs to build the implementation.)

@implementation FirstAppDelegate

The code that follows is the implementation for the FirstAppDelegate class.

@synthesize window;

This line generates setter and getter methods for window.

- (void) applicationDidFinishLaunching:…

This is an implementation for the applicationDidFinishLaunching: method, which is a method defined in the NSApplicationDelegete protocol. The default implementation included in the template is an empty stub. You can expand it with your own application initialization code.

Functionally, you can add custom start-up code to the applicationDidFinishLaunching: method, but none of the other messages that the delegate can handle are implemented — yet. These other methods are introduced later in this chapter. The delegate is a subclass of NSObject. In theory this means you can run any NSObject method on the delegate. This is often useful for other subclasses for NSObject, but not for the application delegate — it's unlikely that you'll want to copy the delegate or release it from memory. In this context, NSObject is subclassed because it's the simplest and easiest way to create a generic Cocoa object that can be customized with further features. Its inherited features are redundant. This isn't usually true when subclassing NSObject; generally, you do want to be able to copy objects and manage them in memory, but the application delegate is a special case.

Note that although the application includes other objects such as a menu, they're not referenced in the code or loaded explicitly by the delegate. When the application loads, the menu loads with it, without alloc events or messages. This is an example of dynamic loading, which was introduced in Chapter 5. The menu is defined in the MainMenu.xib nib file and loaded automatically. This file is described in the next chapter.

window is defined and loaded from the nib file in the same way. The IBOutlet directive tells the compiler to leave the window pointer value empty at compile time. When Cocoa's nib loader loads the object, it loads a valid address into the pointer. Your code can then access the features of window in the usual way. This process is automatic.

IBOutlet NSWindow *window; //Undefined at compile time

//After the nib loader has done its work

//window points to the object called window in the nib file

[window doSomething]; //This is now valid

Discovering Object Methods and Properties

When the application runs, window appears on the screen and waits. By default it does nothing, because there's no supporting code for it. You can control it by adding code that sends messages to it:

[window aHypotheticalMessageThatDoesSomethingInteresting];

But you don't yet know which messages NSWindow responds to. To discover this information, you have to review the NSWindow class reference page in the developer documentation.

Finding and using class references

While you can browse to a class reference by drilling down through the layer and framework hierarchy introduced in Chapter 2, there's a shortcut. In Xcode, choose Help Developer Documentation. This loads the offline version of the documentation built into Xcode. Figure 6.2 shows the window that appears in Xcode 3.2.3.

mac_note.eps Note

If you're using a later version of Xcode, you'll almost certainly see a similar page, but it may have different graphics.

At the top right of the window is a Spotlight-like search field with a magnifying glass. You can search for the NSWindow class reference by typing NSWindow into the search field. But Xcode supports a shortcut that can save you significant time. In the interface file, highlight NSWindow with the mouse and right-click it. You'll see a complex floating menu with many options, as shown in Figure 6.3. Choose Find Text in Documentation. This is equivalent to copying the text, pasting it into the search field, and waiting for the results. This is an immensely helpful feature in Xcode, and you'll find it invaluable.

Figure 6.2

The Xcode documentation Quick Start page includes sample videos. This page changes with every new version of Xcode, so you may not see the graphics or features shown here.

495896-fg0602.tif

When the search completes, the left-hand pane contains a list of search results that mention NSWindow. This list is messy because the search returns all resource types, but you can ignore almost all of it. At the top of the list is an API pane with a C icon next to NSWindow. This icon tells you that a document is a class reference. The documentation browser usually — but not always — displays a class reference if it finds one that matches the search string.

mac_tip.eps Tip

When you type in a search string manually, the search field attempts to search for it immediately, even before you finish typing. This isn't always helpful, but you can use it to search for items that share a starting string. You can also use the Contains, Prefix, and Exact buttons at the top left to further fine-tune the search. You can hide the list at the left by double-clicking NSWindow or the C icon in the API pane, or by dragging the dividing line between the search results and the Table of Contents all the way to the left.

Figure 6.3

The Find Text in Documentation feature can be a huge timesaver. You can use it to search the documentation for any highlighted string.

495896-fg0603.tif

Exploring class references

Class references are single scrollable pages. NSWindow is a complicated class with many features, so the reference is long and detailed. The most efficient way to explore it is to use the Table of Contents list that appears in the pane to the left of the main window.

You can use the reference information in various ways. When looking for a list of possible features, it's useful to scan the Tasks list. It includes a complete list of the messages that NSWindow responds to, grouped by application and function. You can view the Tasks list by scrolling down or by clicking the Tasks header in the Table of Contents. Clicking the reveal triangle expands it to show a summary list in the Contents pane, as shown in Figure 6.4.

Figure 6.4

Clicking the reveal triangle next to the Tasks header displays a list of solution-based summaries for each class. Use the task list as a quick overview of the class's features.

495896-fg0604.tif

mac_tip.eps Tip

At first sight the list of tasks, methods, and other features for some Cocoa objects can appear overwhelming. You don't need to memorize these lists, and it's a given that unless you have developer superpowers, you won't immediately know how to use all the features that appear. Cocoa is a library of connected objects and features. Some are self-explanatory, but others won't make sense until you've learned more about how Cocoa objects work together.

For a first project, you'll attempt a very simple task — changing the window's title string. Scroll down through the list of tasks to find the Managing Titles subheading. As you might expect, this subheading includes a list of features devoted to managing the title bar. Each blue item is a clickable link. setTitle: looks like a possible solution, so click the setTitle: link to read a description of this method.

mac_caution.eps Caution

Methods and properties often match their names in an intuitive way, but sometimes they don't. For example, you might expect the center method to center the window in the screen. In fact it centers it horizontally, but places it above center vertically for prominence and visual impact. To avoid surprises, check the full description of a feature before you use it.

Figure 6.5 shows the setTitle: entry. It has a title followed by a brief description, followed by a code signature. Making sense of the signature is a fundamental skill. Even if you have experience in other object-oriented languages and already understand what each element does, some of the Objective-C syntax may be unfamiliar.

Figure 6.5

The setTitle: method description includes a terse list of the method's features. Very few of the class reference pages include sample code or examples.

495896-fg0605.tif

Understanding signatures

In this example, you want to send the setTitle: message to an instance of NSWindow. The signature tells you four things:

bl.eps The initial “-” character indicates that this is an instance method, not a class method. It must be run on an instance of NSWindow, not on the class as a whole. This is good news — it means you can use this method on window. For a class method, the initial character would be a “+,” and it would have to be run on NSWindow itself, not on an instance.

bl.eps (void) indicates there's no return value. If the method supplied a return value, this field would indicate its type in the usual C-like way.

bl.eps The third field is the name of the method and also defines the string used to trigger it — setTitle:.

bl.eps The colon indicates that a parameter follows the method name. In this example, the parameter is an instance of NSString, which is Cocoa's string object data type. When a parameter is a Cocoa object, it appears as a link. If you need more information about NSString, click on the link to view its class reference.

Putting this information together, you can guess that the message you need to send to window looks like this:

[window setTitle: aString];

aString can be defined as in-line literal using the @ objectification character. So your final code looks like this:

[window setTitle: @”A Window Title”];

Introducing Code Sense

To add this code, type it after the comment line in applicationDidFinishLaunching:. You'll notice that as soon as you type the open square bracket and win, Xcode completes the rest of the word window. Similarly when you type setT, Xcode automatically expands it to setTitle: and also adds a reminder field that tells you that setTitle: takes a parameter string.

This autocompletion feature is called Code Sense. Code Sense doesn't read your mind — this may change in future versions of Xcode — but it does try to predict your typing and insert likely items from the application's symbol table. The table includes the full set of Cocoa objects by default.

Code Sense can dramatically improve your productivity, but it can take a while to get used to having an assistant that makes guesses as you type:

bl.eps To accept a guess, press the Tab key. The cursor moves past the guess.

bl.eps When a symbol has multiple substring options — for example setT could be read as setTitle: or setTitleWithRepresentedFilename: — press the Return key to accept a part-symbol. The cursor moves to the next substring.

bl.eps To set a reminder field, tab to it and start typing. Your input overwrites the reminder string. For classes with multiple parameters, tab to each field in turn.

bl.eps To see a list of matching symbols, press the F5 key. Scroll up and down the list by pressing the keyboard's up and down arrow keys, or select a symbol with the mouse. Click once to highlight a symbol, and double-click to insert it in the code.

bl.eps To ignore a guess, keep typing to overwrite it.

bl.eps When you close a curly bracket, the corresponding opening bracket flashes so you can check for balance. Closing square brackets are sometimes — but not always — added automatically. When brackets don't balance, Code Sense makes a warning sound.

mac_note.eps Note

Code Sense isn't infallible, and square bracket autocompletion is handled in a somewhat random way. However, you always get a warning when brackets don't balance. Code Sense is most useful when it becomes second nature. You will occasionally need to backspace to overwrite wrong guesses, but it's usually more of a help than a distraction.

Figure 6.6 shows the added line in FirstAppDelegate.m and also illustrates what happens if you build and run the modified project. The code does indeed change the title bar. Success!

Figure 6.6

The blank template application with a new window title and the code that creates it.

495896-fg0606.tif

Working with multiple classes

For a more difficult challenge, you can try to maximize the window as soon as the application runs. Apple's human interface guidelines don't support maximization, and windows have no simple maximization method. The green button on an OS X window resizes the window frame to a default size, but this doesn't have the same effect as a simple maximization.

mac_note.eps Note

In theory, according to Apple's guidelines, Cocoa applications should have multiple floating windows. But some users find that a single main window with floating subwindows is more intuitive and easier to use — perhaps because it avoids the popular OS X distraction of accidentally losing application focus by clicking outside a window. Because the UI guidelines aren't policed, you can implement whichever solution you feel most comfortable with.

Ignoring any possible controversy, you'll implement a maximize feature. Without a maximization method, the only way to maximize a window is to set its size manually. Scanning the list of tasks reveals a task called Sizing Windows, which looks as if it might solve part of the problem. But it's worth looking at the Introduction to Window Programming Guide for Cocoa, show in Figure 6.7, to see if it contains further hints.

mac_tip.eps Tip

You can find the Window Programming Guide for Cocoa by scrolling down to the bottom of the NSWindow's Table of Contents. Most classes don't include a Companion Guide, but NSWindow is a key class with many features, and the documentation has been expanded to include a guide that introduces them.

The subsection called How Windows Work introduces the concept of a frame. In Cocoa, a frame is the rectangle that surrounds an object and defines its size and position. Returning to the Sizing Windows subheading shows that it features a number of frame-related methods. frame returns the current frame but is read-only, and it can't be used to set the frame. setFrameOrigin: changes the position of the frame and the position of the window. setFrame: does exactly what we want — calling setFrame: on a window resizes it.

From the setFrame: method description, you can see that its parameters include display, which is a Boolean that defines whether or not the window contents are refreshed, and animate, which enables an optional animation effect. display is irrelevant for an empty window, but you may as well set it to YES in case you add other content later. animate looks like an interesting parameter to experiment with. So your first attempt looks like this:

[window setFrame: aFrame display: YES animate: YES];

Figure 6.7

Guides vary in depth and complexity. The NSWindow guide is very comprehensive, but some guides are short notes with a couple of terse examples.

495896-fg0607.tif

Exploring C-language features

How do you calculate aFrame? According to the documentation, aFrame should be an instance of NSRect. There's no link to NSRect, so the only way to find out more about it is to search for it. You can use the Find Text in Documentation feature to search the documentation. Highlight NSRect in the documentation, right-click to show a floating menu, and select Find Text in Documentation. Figure 6.8 shows the result.

This reveals that NSRect is a C-language typedef, and not an object. You've already fallen out of Cocoa's object hierarchy into an underlying C library that is part of the Foundation layer. NSRect is a Foundation data type. The Foundation library is still part of Cocoa, but it uses C code and data structures and isn't object-oriented.

Figure 6.8

Cocoa's C language functions and data types don't appear as links in the documentation, but you can search for them manually. NSRect is a Foundation data type.

495896-fg0608.tif

Looking for more information about NSRect is unhelpful, because there isn't any. You can drill down further to find out about NSPoint and NSSize. Or you could create your own custom NSRect implementation and use it to convert a group of floats or ints into an NSRect.

There's a simpler solution. The Foundation layer includes a list of Foundation Functions that support its data types. You can use the NSMakeRect() function to create an NSRect from arbitrary height, width, and position floats.

mac_caution.eps Caution

Cocoa data types are often deeply nested. NSRect includes an NSPoint made from two CGFloats , which are plain C floats redefined. It's normal to drill down through multiple data structures to find that the underlying data types are familiar C data types buried under multiple Cocoa redefinitions.

Discovering framework functions and data types

Unfortunately, the documentation fails to introduce the Foundation Functions when you look up information about a Foundation Data Type. You can only learn about them by looking through sample code or finding them in an online discussion — or reading about them in a book.

Many frameworks offer a selection of helper functions and data types. These are critically useful, but difficult to find unless you already know they exist.

For example, the Quartz 2D graphics library includes a collection of CGGeometry features that provide the functions and data types used to implement low-level graphics.

When you start working with a new framework, it's obligatory to check its Framework Reference page to review its functions and data types. This won't always lead you to the features you need to solve a problem because you may need to look for related frameworks to find them. For example, NSWindow is part of the Application Kit framework shown in Figure 6.9. Reviewing the Application Kit Functions Reference page won't tell you that it relies on the Foundation Functions, shown in Figure 6.10, for various support functions.

But as a rule of thumb, the Foundation Functions are used throughout Cocoa. It can be helpful to review them whenever you're exploring new features.

Figure 6.9

The Application Kit Functions Reference lists some of Cocoa's less obvious features. You won't find these functions unless you look for them, but they're critically useful to many of Cocoa's features.

495896-fg0609.tif

Figure 6.10

Similarly, the Foundations Functions Reference page lists a selection of other essential Cocoa functions. The task list is particularly revealing. You won't use these functions regularly, but you should know that they exist and be familiar with what they can do.

495896-fg0610.tif

Having explored the Foundation Functions, your revised code looks like this:

[window setFrame: NSMakeRect(x,y,w,h) display: YES animate: YES];

This resizes the window to an arbitrary size you can specify using static floats for x, y, w, h. Experimenting with this line reveals something unexpected — setting x and y to 0 anchors the window at the bottom of the screen, not at the top. This is because OS X windows use a bottom-left origin. To move a window to the top of the screen, the bottom coordinate must be calculated. For example, on a screen with a resolution of 1024 × 768, Figure 6.11 shows the result of

[window setFrame: NSMakeRect(0,0,640,480) display: YES animate: YES];

This simple problem is beginning to look more challenging. Not only is there no simple maximize feature, but also fixing the top of a window requires some extra coordinate transformations. If you want to anchor the window and resize it to fill the screen, you need to know the screen resolution. Is there a better solution?

Figure 6.11

A first attempt at resizing the window succeeds in changing its size, but doesn't calculate the size from the screen dimensions.

495896-fg0611.tif

Expanding the search to related classes

For certain applications, the answer turns out to be “No”: some features can't be implemented easily or elegantly. You may need to resort to hacks or other less than optimal solutions.

But in this example, if you review NSWindow's Table of Contents again, you'll see a heading called Accessing Screen Information. The first method in this Task is called screen and returns the screen the window is in as an instance of an NSScreen object.

NSScreen *thisScreen = [window screen];

Clicking through to the NSScreen class reference reveals that NSScreen implements a method called frame that returns an NSRect for the screen's frame. So you can use the fragment

[thisScreen frame]

to return an NSRect. This is almost perfect, but the NSRect returned by frame covers the entire screen. Looking again reveals another method called visibleFrame that returns the area that excludes the standard OS X menu at the top of the screen and the Dock at the bottom. This is exactly the area you want and solves the problem.

You can now write a new version that gets the visible frame from the current screen and passes it back to window:

NSScreen *thisScreen = [window screen];

[window setFrame: [thisScreen visibleFrame] display: YES animated: YES];

It's possible to simplify this code to a single line. There's no need to make thisScreen an explicit pointer; you don't access it again. So you can use a nested return:

[window setFrame: [[window screen] visibleFrame] display: YES animated: YES];

To recap how the nested code works:

[window screen]

//returns the current screen as an NSScreen

[[window screen] visibleFrame]

//runs the visibleFrame method on the screen, returning an NSRect

[window setFrame: [[window screen] visibleFrame]…];

//passes the NSRect to setFrame and sets the size and position of window

You can repeat this process to explore the other features of NSWindow. If you look through the class reference, you can find methods that enable or disable the drop shadow, modify the window opacity, close or minimize the window, and so on. Some methods are simple but powerful — for example, print sends the window contents to the OS X printing system and implements the features of a basic printing solution. Others are more complex and require a deeper knowledge of Cocoa and its messaging system.

mac_tip.eps Tip

It's an excellent idea to experiment with some of NSWindow's other methods, to familiarize yourself with the class's features.

Receiving messages from OS X with a delegate

window is a Cocoa object embedded in OS X. Your application controls it by sending messages to it. But how can your application handle messages that come from OS X? You've looked briefly at the application delegate and seen that it implements a handler for a single OS X message called applicationDidFinishLaunching:. Other messages can be handled by adding further handlers. But where can you find a list of those messages?

Working with protocols

Looking again at the code for the interface, you can see the NSApplicationDelegate protocol.

#import <Cocoa/Cocoa.h>

@interface FirstAppDelegate : NSObject <NSApplicationDelegate> {

NSWindow *window;

}

@property (assign) IBOutlet NSWindow *window;

@end

The <NSApplicationDelegate> statement in the second line tells the compiler that the FirstAppDelegate class can use methods bundled inside the NSApplicationDelegate protocol.

You can think of a protocol declaration as a terse but powerful #include statement. Using an imaginary protocol called AnImaginaryProtocol

@interface AnObject: NSObject <AnImaginaryProtocol>

is almost equivalent to:

#import <Cocoa/Cocoa.h>

@interface AnObject : NSObject {

}

//List of <AnImaginaryProtocol> delegate methods starts here

- (void)anOptionalMethod;

- (void)anotherOptionalMethod;

- (AClass *) anOptionalMethodThatReturnsAValue;

+ (void) andSoOn…;

//List of <AnImaginaryProtocol> delegate methods ends here

@end

In a sense, a protocol is a convenient way to skip unnecessary copying and pasting in a class interface. When you adopt <AnImaginaryProtocol>, the methods between the comments are pasted into the interface automatically. You can then add implementation code for them in the class implementation.

But protocol methods get a special, useful dispensation from the compiler. Unlike conventional methods, they don't have to be matched by a corresponding implementation. Instead you can pick and choose which protocol methods to implement. It's equally valid to add implementation code for all, some, or none of the methods adopted from a protocol. The compiler flags a warning if some of the methods aren't implemented, but you can build a project without implementing any of them.

This makes protocols and delegates immensely responsive. Without delegation or protocols, an object must be packed with empty method stubs for every possible message it can receive. With delegation, code is only required for methods that are actually implemented. Figures 6.12 and 6.13 illustrate how this works.

Figure 6.12

Without a protocol, a class has to include a list of all possible method definitions in the interface and a list of all possible method stubs in the implementation.

495896-fg0612.eps

Figure 6.13

With a protocol, optional methods can be bundled into a single simple #include statement and only included in the implementation when needed.

495896-fg0613.eps

Many Cocoa objects have associated delegate protocols. You can also define custom protocols for your own objects. NSApplicationDelegate is a standard Cocoa protocol, so you can find a list of definitions of its optional methods in the developer documentation. Protocol Reference pages are similar to Class Reference pages, and you can search them in the same way — by copying and pasting search words into the documentation search field or by using the Find Text in Documentation menu option. Figure 6.14 shows the NSApplicationDelegate protocol reference page.

Figure 6.14

Like a class reference, a protocol reference displays methods grouped into tasks. There's also a conventional alphabetical list of the methods in the protocol.

495896-fg0614.tif

Reading a Protocol Reference

A Protocol Reference page is like a Class Reference page, and it is organized in a similar way. Protocols aren't objects, so they don't have properties, but they do include a list of methods grouped by tasks. You can use the Table of Contents at the top left of the page to review the tasks, and you can also access an alphabetical list lower down on the page.

The messages shown in this reference are generated automatically by OS X at various points in an application's lifecycle. We've already used the applicationDidFinishLaunching: method, which is triggered after the application loads. This stub is included in the template as a convenience because this method is used in so many applications. If you don't need to use this feature, you can delete the stub. You can also add further delegate methods, as shown below. An object can adopt an unlimited number of protocols, although in practice if an object is adopting more than three protocols the application's design may need to be simplified. To adopt multiple protocols, separate them with commas:

@interface AnObject: NSObject <AnImaginaryProtocol, ACompletelyDifferentProtocol, YetAnotherProtocol>

If an object adopts multiple Cocoa protocols, it will receive all the corresponding messages sent by OS X. You can also define your own protocols and send messages from them to other custom objects in your application.

Understanding delegation in Cocoa

Delegates and protocols are completely general and not limited to high-level application management. In theory, a delegate object can do anything a normal object can do, and there are no restrictions on protocol methods.

In practice, most Cocoa objects use delegation in a selective way. These options were listed in Chapter 2, but they're important enough to repeat here.

Delegate messages are sent:

bl.eps When an event is about to happen

bl.eps When an event has happened

bl.eps To ask if an event or response should happen

bl.eps To request data from another object

If you review the list of methods defined in the NSApplicationDelegate protocol reference, you'll see they fit into one of these groups:

bl.eps applicationDidFinishLaunching: is received by the application delegate when the application has finished loading.

bl.eps applicationDidChangeScreenParameters: is received when the screen resolution changes.

bl.eps applicationShouldTerminate: is received when OS X wants to know if an application should quit. The application delegate controls the response by returning YES or NO. You can use this feature to keep an application running while it saves its state before quitting.

bl.eps applicationWillUnhide: is received when the application is unhidden.

This event-based model describes how delegates are often used in Cocoa. You won't find it explained in the documentation, but it's much easier to understand delegates and protocols if you appreciate these design goals.

Other objects that support delegation follow this model. Elsewhere in Cocoa you can find delegate messages that are sent:

bl.eps Before a menu opens

bl.eps After a window is hidden

bl.eps To return a method for a given key triggered by an animation event

bl.eps To return image data

bl.eps When an audio player objects finishes playing a file

This list is a very short selection of Cocoa's delegate features. You can find the full list of objects that support a delegate and their associated protocols by searching the documentation for “protocol reference.”

Implementing delegate methods

Now that you've been introduced to the methods in NSApplicationDelegate, you can experiment with adding them to the application. The fastest and simplest way to implement a delegate method is to copy its signature directly from the documentation and paste it into a class implementation.

As an exercise, you'll implement applicationWillHide:, which is triggered when the user hides the application's window. Find the method in the task list and click it to see the full definition. Highlight the signature with the mouse as shown in Figure 6.15 and press Ô+C to copy it.

Press Ô+V to paste it into the implementation file, as shown in Figure 6.16. Because it's the start of a new method block, make sure it's pasted before the @end directive but after the curly bracket that closes applicationDidFinishLaunching:. The window-maximizing code has been removed because it's not used in this example.

mac_note.eps Note

aNotification is a notification object passed by OS X to this method. It arrives as a received parameter. In this example, you'll ignore it because it doesn't contain any useful information. Notifications are system-level events generated and managed by OS X. Don't confuse them with Objective-C messages — the two messaging systems are unrelated. For more about notifications, see Chapter 9.

There's no limit to the possible complexity of the method implementation. In a commercial application, this method might halt a running timer to save processor cycles, write the application state to disk ready for a fast restore when the application unhides, or even send an e-mail. You'll create an implementation that's very much less sophisticated — one that logs a message to the console.

Figure 6.15

To implement a method, copy and paste its signature from the documentation. You can also type it in manually, but copying and pasting is quicker and less error prone. All parameter names are local. If they clash with existing names, add an underscore.

495896-fg0615.tif

Implement the method code with a call to NSLog:

- (void) applicationWillHide: (NSNotification *)aNotification {

NSLog (@”You hid the application!”);

}

mac_note.eps Note

NSLog is Cocoa's console output object. You can write the console with printf, but NSLog includes extra features that can convert object values into formatted strings. It's the simpler and more powerful choice for Cocoa debugging. Don't forget to end the method with a curly bracket. Also, note that Code Sense doesn't autocomplete NSLog itself, but does add a string reminder once it recognizes it. This is a persistent bug in Xcode.

Figure 6.16

Pasting a method signature creates the first part of the method implementation. To complete the implementation, add code between the curly brackets.

495896-fg0616.tif

To view the output, open Xcode's console window by choosing Run Console. The console window includes a convenient copy of Xcode's main toolbar. It can't be customized, but you can use Build and Run to build and run the project without having to switch back to the main Xcode window.

Click Build and Run. When the window appears, choose First Hide First to hide the application. Figure 6.17 shows how the result.

The applicationWillHide: method is triggered by OS X, running your new code. The code you added posts a console message. To unhide the application, click its icon in the Dock. If you hide it again, the method is triggered again. Optionally, you can experiment with some of the other methods defined in the protocol. For example, you can use applicationWillUnhide: to log a message when the application unhides and at least one of its windows becomes visible.

Figure 6.17

Hiding the window makes it disappear into the Dock, but because the application is running as an Xcode subprocess, the console window continues to be visible — and it displays this message.

495896-fg0617.tif

mac_note.eps Note

applicationWillHide: is triggered before the window disappears. applicationDidHide: is triggered after the window disappears. Cocoa often gives you both options for important events. In this example they're functionally identical. In a different context, you might want to use applicationWillHide: to stop animations or other foreground processes before allowing the window to disappear.

Receiving messages from OS X with NSResponder

OS X uses a variety of mechanisms to send messages to an application. Application control messages don't process mouse event information — so how can you make an application respond to mouse clicks or movements? Reviewing the Table of Contents for NSWindow shows that there's a Handling Mouse Events task. This includes a mouseLocationOutsideOfEventStream method, which returns the position of the mouse cursor inside the window on demand.

You could put this method inside a loop and poll it continuously to monitor the mouse position. But this would be inefficient, and it wouldn't handle mouse clicks. Given Objective-C's event-based programming model, it's realistic to assume that there's an event-based solution.

Interface objects — including windows — can use the NSResponder class to respond to user actions. Scrolling to the top of NSWindow's class reference reveals the Inherits from… list. This tells you that NSWindow is a subclass of NSObject, and also of NSResponder. It also means that NSWindow includes all the properties and methods of NSObject and NSResponder “for free.”

Every class that handles user messages in Cocoa is a subclass of NSResponder. So you can use its features to make window respond to mouse clicks. Figure 6.18 shows the mouse event methods listed in the NSResponder class reference.

Figure 6.18

By subclassing NSResponder — where it's not subclassed already — you can handle a rich selection of mouse and other event messages.

495896-fg0618.tif

mac_caution.eps Caution

NSResponder is an abstract class. You can't create an instance of NSResponder, but you can create instances of its subclasses. Effectively, NSResponder is a bundle of methods that implements event handling for window, view, and application objects. You can subclass it to create your own event-driven objects. Developers occasionally debate whether NSResponder would be better implemented as a protocol. The answer may well be “yes,” but it's implemented as an abstract class, so that's how it has to be used.

Like protocol methods, the NSResponder methods are designed to be overridden. You use them by copying their signature, adding them to a receiver, and then implementing them with custom code. In this example, you want to make window respond to mouse events, so window is the receiver. The code

[window mouseDown: theEvent];

doesn't work because it sends a message to the window. You're looking for a message handler in the window.

mac_tip.eps Tip

You can use this code to send simulated mouse clicks to a window. In fact, you can trigger any NSResponder method artificially by running the method from your code. Most applications don't need this, but you can use this feature to create mouse and key event automation.

But where does the code go? There are no class files for NSWindow. How can you modify it?

Subclassing NSWindow

An obvious solution is to subclass NSWindow and extend it with a mouse-handling method. If you subclass NSWindow, you can add one or more custom methods to it, including some of the NSResponder methods.

Xcode includes features that make it easy to add a subclass to a project. Right-click the Classes group at the top of the Groups & Files pane in Xcode and choose Add New File, as shown in Figure 6.19. You can also choose File New File from the main menu.

Select Cocoa Class in the Mac OS X pane at the left, and select Objective-C class in the list of file types at the top, as shown in Figure 6.20. Locate the Subclass Of drop-down list halfway down the window, and click NSObject if it's not already selected.

Figure 6.19

Adding a new class in Xcode. If you right-click a group, the class is automatically added to the selected group. This isn't always a good thing. If the class includes a nib file, you'll have to drag it to the Resources group by hand.

495896-fg0619.tif

Click Next at the bottom right and enter a filename in the box at the top of the window. Because you're subclassing NSWindow, it's a good idea to use a name that reminds you that this is a window-like class, such as FirstNewWindow. Click Finish to add the new class to the project. Figure 6.21 shows the result.

mac_tip.eps Tip

Xcode doesn't enforce a naming convention or add a project prefix to new files. For clarity, it's useful to follow a standard naming convention such as <projectname><subclassname><object type>. Including the project name makes it easier to keep track of files when you begin reusing them in other projects.

Figure 6.20

Think of NSObject as a blank generic subclass. The other subclass options shown here include prewritten method stubs. The NSObject option creates a blank subclass without prewritten code.

495896-fg0620.tif

By default, this process creates a subclass of NSObject. This isn't what you want here, so as a first step you need to change the interface code in FirstNewWindow.h. When the file is created, the first line of the interface looks like this:

@interface FirstNewWindow : NSObject {

To redefine your new class as a subclass of NSWindow instead of NSObject, change the code to

@interface FirstNewWindow : NSWindow {

Save the file, and that's it — you've subclassed NSWindow. You can repeat these steps to subclass any other Cocoa object.

Figure 6.21

Exploring the new subclass. Currently it's still a subclass of NSObject.

495896-fg0621.tif

mac_tip.eps Tip

When you create a new file in Xcode, you can use the drop-down menu to select NSObject, NSDocument, NSView, or NSWindowController as the source class. These classes are subclassed regularly, so they're built into Xcode to save you time. Choosing NSObject creates a blank header and implementation file. The other classes generate files that include stub definitions of some of their key methods.

Now that you have subclassed NSWindow, you can extend it by overriding one of the NSResponder mouse methods. You'll use mouseDown:. As before, the easy way to do this is to copy and paste the signature from the method signature from the class reference into the new class.

In Xcode, select FirstNewWindow.m. Paste the line under the @implementation FirstNewWindow directive so that the file looks like this:

-(void)

@implementation FirstNewWindow

-(void)mouseDown: (NSEvent *) theEvent

@end

Add curly brackets to the method and then add a line that uses NSLog to send a message. The finished method looks like the version shown in Figure 6.22.

Figure 6.22

Implementing the mouseDown: method in your new subclass of NSWindow. The code is correct — but it doesn't work!

495896-fg0622.tif

Build and run the file. Open the console window by choosing Run Console in the main Xcode window. When the application window appears, click in it.

mac_caution.eps Caution

Whenever you click Build and Run after making an edit, Xcode displays a Save before Building? dialog. Usually you'll want to click Yes to save the edited files. If you click Cancel, the files won't be saved. Xcode always builds from the saved files, not the versions visible in the editor. You can also use Cancel if you realize you need to make more edits before a build.

Unfortunately, also as shown in Figure 6.22, nothing happens in the console window. Clicks have no effect. You've subclassed the window object, and you've added a mouse method, so why isn't the code working?

There are two ways to make the code active. One uses a feature in Interface Builder and is introduced in the next chapter. The other gives you the power to modify the features of NSWindow in place, without subclassing. It's called creating a category on a class.

Creating a category on NSWindow

Think of a category as an in-place subclass. Instead of copying the features of NSWindow to a subclass with a different name, you can create a category to add new methods directly to NSWindow.

mac_tip.eps Tip

Don't be confused by the name. Nothing is categorized, and there are no categories in the usual sense. A category is an in-place subclass that adds extra methods. It might as well be called a zombo or some other made-up word.

Understanding categories

Categories make it possible to modify and extend Cocoa objects without changing their class. This can be useful when Cocoa objects enforce strong property typing and refuse to accept a subclass.

For example, NSWindow has a setContentView: method that takes an instance of NSView. The compiler generates a warning if you try to pass a subclass of NSView. In this example you can ignore the warning and your code will work — probably. But other Cocoa classes are less forgiving of type mismatches.

When you create a category, you change the properties of the class as a whole. If you create a category on NSWindow, every window in the application is modified.

Creating a category

To create a category, follow these steps:

1. Create a new file, using the steps required to create a subclass.

2. Save the file as <classname>+<category name>. For example, a category on NSWindow should be saved as NSWindow+<aName>. The category name is arbitrary.

3. In the interface file, add a list of the methods you are adding to the class.

4. In the implementation, add the code that implements each method.

For example, to extend NSWindow with a maximize method, the interface looks like this:

#import <Cocoa/Cocoa.h>

@interface NSWindow (maximize)

-(void) maximize;

@end

The (maximize) after NSWindow is arbitrary. The name doesn't have to match the method or methods in the interface.

The implementation looks like this:

#import “NSWindow+Maximize.h”

@implementation NSWindow (maximize)

-(void) maximize {

[self setFrame:[[self screen] visibleFrame]

display: YES

animate: YES];

}

@end

After you add this category to your project, you can maximize any window in your application with

[aWindow maximize];

You can repeat steps 3 and 4 to add your custom mouseDown method to NSWindow.

mac_note.eps Note

Sample code for this chapter is available on the Web site at www.wiley.com/go/cocoadevref.

Summary

You've learned a lot in this chapter, so take some time to review your new skills. You looked at the structure of a Cocoa application and learned about the role of the delegate object. You were introduced to protocols, discovered some of their possible applications, and explored how to use delegate objects and protocols in practice.

You encountered NSResponder for the first time, and learned how to implement its methods in a subclass of NSWindow. You were taken through a step-by-step demonstration of subclassing, were introduced to categories, and discovered that important parts of every Cocoa application are defined in its nib files, which are explored in the next chapter.