Cocoa uses a technique called delegation to allow objects to specify other objects, called delegates , to handle certain messages for them. Thus, one object can delegate to another object the responsibility for handling messages of a certain type. Delegation gives the programmer a system for modifying or controlling the behavior of Cocoa’s more complicated objects, such as those of type NSApplication and NSWindow, without having to subclass them. Typically, delegation is used to control the behavior of an object or to invoke a method automatically in response to an action performed by a user.
An object sends its delegate specific messages under specific circumstances. Before the object sends the message, it checks to see if the delegate can respond to the message by interrogating the delegate with therespondsToSelector: message. If the delegate doesn’t implement a method for a specific message, the message simply isn’t sent! (The sender must interrogate the delegate, because if a delegate receives a message for which it doesn’t have a corresponding method, the program will generate an error.)
For more information about respondsToSelector: and similar messages, see the documentation for the NSObject class. The respondsToSelector: method is defined in the root NSObject class and thus is inherited by all other objects.
Cocoa has three principal types of messages that are sent to delegate objects: Should , Will, and Did messages. Both Should and Will messages are sent before something happens (e.g., before a window closes). When the delegate is sent a Should message, the delegate can change the sender’s behavior by responding in a certain way. On the other hand, a Did message gets sent after a particular event takes place (e.g., after a window closes). A Did message notifies the delegate object that something has occurred. At that point, it’s too late to change the behavior, but you might want to do something in response to an action that another object has taken.
There are dozens of different delegate messages implemented by various Cocoa classes. In Tables 7-1 and 7-2, we list a few typical Will, Should, and Did delegate methods that are implemented by the NSApplication object. In these tables, as in earlier ones, we’ll use the same type conventions used in Apple’s Cocoa documentation: methods are in bold type, arguments are in italic type, and data types are in normal type.
Table 7-1. Typical Will and Should messages
Will message |
When message is sent to delegate |
---|---|
(void)applicationWillFinishLaunching: (NSNotification *)aNotification |
Before the application is finished launching. The argument to this delegate method (and many others) is an NSNotification object from the Foundation. |
(void)applicationWillUnhide: (NSNotification *)aNotification |
Before the application is unhidden. |
(void)applicationWillUpdate: (NSNotification *)aNotification |
Before the Application object updates the application’s windows. |
(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *)sender |
When the NSApplication object receives a terminate: message. This method lets you clean up the application — for example, shutting down databases and saving or closing any open files before it terminates. Return NSTerminateNow to allow the application to terminate now, NSTerminateCancel to cancel termination, or NSTerminateLater to ask for more time. (If the delegate responds with NSTerminateLater, it will be sent another applicationShouldTerminate: message.) |
Table 7-2. Typical Did messages
Did message |
When message is sent to delegate |
---|---|
(void)applicationDidBecomeActive: (NSNotification *)aNotification |
After the application is activated |
(void)applicationDidHide: (NSNotification *)aNotification |
After the application is hidden |
(void)applicationDidFinishLaunching: (NSNotification *)aNotification |
After the application has been launched and initialized, but before it receives its first event |
(void)applicationDidResignActive: (NSNotification *)aNotification |
After the application is deactivated |
(void)appDidUnhide: (NSNotification *)aNotification |
After the application is unhidden |
(void)appDidUpdate: (NSNotification *)aNotification |
After the Application object updates the application’s windows |
Delegate methods let you do fairly complicated things with ease. For example, suppose that you set up a delegate object for your application’s NSApplication object, and that the delegate implements the applicationShouldTerminate: method. When the user chooses the Quit menu command from your application, the NSApplication object receives the terminate: message and in turn sends the applicationShouldTerminate: message to its delegate. The delegate’s applicationShouldTerminate: method could then display a panel asking the user “Do you really want to quit?” If the user answers “No,” the delegate returns NSTerminateCancel to the NSApplication object, and the application doesn’t terminate. If the user answers “Yes,” the delegate returns NSTerminateNow, and the application terminates.
Not every class in the AppKit makes use of delegation, but many do. Frequently, you can accomplish the same thing with either delegation or subclassing. If you have a choice, use delegation! Delegation is simpler and generally easier to debug than subclassing, and it makes your code more easily reusable. Delegation frees you from having to subclass a lot of the AppKit classes. For most applications, you’ll need to subclass only the NSObject and NSView classes.
One of the most frequently used of the NSApplication delegate methods is applicationDidFinishLaunching: , which is automatically sent after an application is initialized but before it receives any events. Later in this chapter, we’ll set up a delegate for our Calculator’s NSApplication object and use applicationDidFinishLaunching: to set the initial radix and perform other initialization tasks.
An object’s
delegate is specified by an
outlet instance variable appropriately called
delegate
. Table 7-3 lists the
main AppKit classes that support
delegates.
Table 7-3. Main delegate-supporting AppKit classes
Class |
Reason for delegate object |
---|---|
NSApplication |
To receive information about the application’s state |
NSBrowser |
To fill the information stored in the browser |
NSDrawer |
To control the display and hiding of drawers |
NSFontManager |
To receive alerts when fonts are changed |
NSImage |
To notify the application if an image isn’t drawn |
NSLayoutManager |
To alert when the text in a container has been laid out, or when a layout has been invalidated |
NSMatrix |
To edit information stored inside a matrix |
NSSavePanel |
To validate filenames |
NSSound |
To alert when sounds are finished playing |
NSSplitView |
To control resizing |
NSTableView |
To control when table rows and columns are selected, displayed, and moved |
NSTabView |
To alert when tabs are displayed and hidden |
NSText |
To control editing and interception of keystrokes |
NSTextField |
To control editing and interception of keystrokes |
NSTextStorage |
To control the processing of edits |
NSTextView |
To control the editing of text |
NSToolbar |
To control the display of toolbars and the addition and removal of toolbar items |
NSWindow |
To receive window events and control resizing |
There are two ways to provide an object with a delegate:
Which object should be the delegate? The answer to this question depends on your application. Sometimes you will create a special object whose sole purpose is to be the delegate of one or more other objects. By using one object as the delegate for several other objects, you can centralize control for handling events for common objects. However, an object can also serve double duty, both being the delegate for another object and having a life of its own.
In our example, we’ll make our Calculator’s Controller object be the delegate of the NSApplication object. We’ll do this for two reasons. First, the Controller class is still fairly simple. By making it the NSApplication’s delegate, we eliminate the complexity of creating a second class.[20]
The second reason to make our Controller object the NSApplication’s delegate is that the initialization we want to perform — namely, setting the Calculator’s radix — needs to be done inside the Controller object itself. Thus, the Controller is the logical object to be the NSApplication’s delegate object.
We’ll use IB to
make our application’s Controller instance the
delegate of the NSApplication object. Recall that the
File’s Owner object under the Instances tab in the
MainMenu.nib
window represents the
Calculator’s NSApplication object;
it’s the owner of MainMenu.nib
.
In IB, select the Instances tab in the
MainMenu.nib
(Nib File) window.
Control-drag from the File’s Owner icon to the
Controller instance icon inside the MainMenu.nib
window.
Double-click the delegate
outlet to complete the
connection, as shown in Figure 7-9.
Our Controller instance is now the delegate of the NSApplication
object referred to as the File’s Owner in IB. Next
we’ll add a new outlet to the Controller class so
that the delegate method can determine the initial menu item (radix)
that is selected in the pop-up menu.[21] This outlet, which
we’ll call radixPopUp
, will be
set to the id of the NSPopUpButton that causes the pop-up menu of
radixes to be displayed.[22]
Back in PB, insert the radixPopUp
outlet into
Controller.h
:
...
@interface Controller:Object
...
int radix; IBOutlet id radixPopUp;
}
...
Save the edited Controller.h
file.
Back in IB, make sure the Controller class is selected in the Classes
pane in the MainMenu.nib
window and then choose
Classes → Read Controller.h to parse the Controller
class definition again (so IB knows about the new
radixPopUp
outlet).
Select the Instances tab in the MainMenu.nib
window.
Connect (Control-drag from) the Controller instance icon to the
pop-up menu and double-click the radixPopUp
outlet
in the NSPopUpButton Connections Info dialog, as shown in Figure 7-10.
Note that this connection is in the opposite direction of a previous connection between these same two objects.
Two connections are listed in the bottom pane of the Connections Info dialog in Figure 7-10 (we set up both of them). This is useful information for Cocoa programmers, because it lets you immediately see the destinations of all of the outlets that have been set for a given class (Controller, in this case). If you click on one of the connections listed, IB will redisplay the connection line for you.
Next, we’ll set up the delegate method in the Controller class.
To receive the application delegate’s applicationDidFinishLaunching:
message in the Controller, all we need
to do is create a method with the name applicationDidFinishLaunching:. You can place
this method between the @implementation
Controller
and @end
statements.
Alternatively, you can create a second set of
@implementation
and @end
statements containing the delegate methods (that’s
what we’ve done below). This separate definition
helps isolate those methods specifically for delegation from the
methods used for other purposes.
You can add as many Controller delegate methods as you want between
the @implementation
Controller(ApplicationNotifications)
and
@end
directives.
ApplicationNotifications is called a
category
.
You can also use this syntax construct for adding methods to AppKit
classes, but you will need to set up separate interface and
implementation files. You cannot use a category to add new instance
variables to a class, but methods in a category have full access to
all of the instance variables defined in the object class itself.
Next we show the matching class interface for the
Controller(NSApplicationNotifications) category:
When the application starts up, the Controller’s
applicationDidFinishLaunching:
method is automatically executed. The method will set the
radix
[23] and
then invoke the Controller’s clearAll: method to display the initialized X
value in the Calculator’s text area.
Build and run your application. In contrast to the last time, your Calculator should display the numbers as soon as you start clicking the digit buttons. However, there are other problems that we need to fix. We’ll address them in the next section.
[20] Creating too many classes to solve a particular problem is a common mistake made by some people new to object-oriented languages. It leads to lasagna code — code with too many layers stacked together. Too many classes may lead to the object-oriented equivalent of spaghetti-code. Creating too few classes is also a common mistake of newcomers.
[21] In this example,
we have the Controller class determine the initialization value from
the pop-up menu in the nib, rather than setting the pop-up to reflect
a value that might be stored in the Controller.m
file. Cocoa is a visual application development
environment; when possible, the default values of instance variables
should reflect what is stored in the visual interface, rather than
the other way around.
[22] When you drag out a pop-up menu from IB’s palette, you actually drag out two objects: an NSPopUpButton and an attached NSMenu.
[23] If we used a matrix of radio buttons instead of a pop-up menu, we could simply call the setRadix: method and supply the id of the matrix as the sender. Unfortunately, the NSPopUpButton uses the selectedItem method to find which cell is currently selected, rather than the selectedCell method.
18.117.189.228