Delegation

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.

Should, Will, and Did Delegates

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.

Specifying an Object’s Delegate

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:

  • Connect the object to its delegate in IB.

  • Use the object’s setDelegate: method.

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.

Setting Up a Delegate Outlet in the Nib

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.

  1. In IB, select the Instances tab in the MainMenu.nib (Nib File) window.

  2. Control-drag from the File’s Owner icon to the Controller instance icon inside the MainMenu.nib window.

  3. Double-click the delegate outlet to complete the connection, as shown in Figure 7-9.

Delegate connection from File’s Owner to Controller

Figure 7-9. Delegate connection from File’s Owner to Controller

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]

  1. Back in PB, insert the radixPopUp outlet into Controller.h:

    ...
    @interface Controller:Object
    ...
        int         radix;    IBOutlet id radixPopUp;
    }
    ...
  2. Save the edited Controller.h file.

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

  4. Select the Instances tab in the MainMenu.nib window.

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

Connecting Controller instance to pop-up menu with radixPopUp outlet

Figure 7-10. Connecting Controller instance to pop-up menu with radixPopUp outlet

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.

Adding the Delegate Method to the Controller

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.

  1. Add all of the following code after the original @end directive in Controller.m :

                            @implementation Controller(ApplicationNotifications)
    -(void)applicationDidFinishLaunching:(NSNotification*)notification
    {
        radix = [ [radixPopUp selectedItem] tag];
        [self clearAll:self];
    }
    @end

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:

  1. Insert all of the following declaration code after the original @end directive in Controller.h.

                            @interface Controller(NSApplicationNotifications)
                            -(void)applicationDidFinishLaunching:(NSNotification*)notification;
                            @end

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.

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

  2. Quit Calculator when you’re done playing around with it.



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

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.189.228