Chapter 11. Using Notifications

A user may have several RaiseMan documents open when he decides that it is too hard to read them with a purple background. The user opens the preferences panel, changes the background color, but then is disappointed to find that the color of the existing windows doesn't change. When the user sends you an e-mail about this problem, you reply, “The defaults are read only when the document window is created. Just save the document, close it, and open it again.” In response, the user sends you a mean e-mail. It would be better to update all of the existing windows. But how many are there? Will you have to keep a list of all open documents?

What Notifications Are

The task is actually much easier than that. Every running application has an instance of NSNotificationCenter. The NSNotificationCenter functions much like a bulletin board. Objects register as interested in certain notifications (“Please write me if anyone finds a lost dog”); we call the registered object an observer. Other objects can then post notifications to the center (“I have found a lost dog”). That notification is subsequently forwarded to all objects that are registered as interested. We call the object that posted the notification a poster.

Lots of standard Cocoa classes post notifications: Windows send notifications that they have changed size. When the selection of a table view changes, the table view sends a notification. The notifications sent by standard Cocoa objects are listed in the online documentation.

In our example, you will register all of your MyDocument objects as observers. Your preference controller will post a notification when the user chooses a new color. When sent the notification, the MyDocument objects will change the background color.

Before the MyDocument object is deallocated, you must remove it from the notification center's list of observers. Typically, this is done in the dealloc method.

What Notifications Are Not

When programmers first hear about the notification center, they sometimes think it is a form of interprocess communications. They think, “I will create an observer in one application and post notifications from an object in another.” This scheme doesn't work, however: A notification center allows objects in an application to send notifications to other objects in that same application. Notifications do not travel between applications.

NSNotification

Notification objects are very simple. A notification is like an envelope into which the poster will place information for the observers. It has two important instance variables: name and object. Nearly always, object is a pointer to the object that posted the notification. (It is analogous to a return address.)

Thus, the notification also has two interesting methods:

- (NSString *)name
- (id)object

NSNotificationCenter

The NSNotificationCenter is the brains of the operation. It allows you to do three things:

  • Register observer objects.

  • Post notifications.

  • Unregister observers.

Here are some commonly used methods implemented by NSNotificationCenter:

+ (NSNotificationCenter *)defaultCenter

Returns the notification center.

- (void)addObserver:(id)anObserver
           selector:(SEL)aSelector
               name:(NSString *)notificationName
             object:(id)anObject

Registers anObserver to receive notifications with the name notificationName and containing anObject (Figure 11.1). When a notification of the name notificationName containing the object anObject is posted, anObserver is sent an aSelector message with this notification as the argument:

  • If notificationName is nil, the notification center sends the observer all notifications with an object matching anObject.

  • If anObject is nil, the notification center sends the observer all notifications with the name notificationName.

Registering for Notifications

Figure 11.1. Registering for Notifications

The observer is not retained by the notification center. Notice that the method takes a selector.

  • - (void)postNotification:(NSNotification *)notification
    
  • Posts a notification to the notification center (Figure 11.2).

    - (void)postNotificationName:(NSString *)aName
                           object:(id)anObject
    
    Posting a Notification

    Figure 11.2. Posting a Notification

  • Creates and posts a notification.

    - (void)removeObserver:(id)observer
    
  • Removes observer from the list of observers.

Posting a Notification

Posting a notification is the easiest step, so you will start there. When your PreferenceController object receives a changeBackgroundColor: message, it will post a notification with the new color. Name the notification @"BNRColorChanged". (Experienced programmers put a prefix on the notification so that it doesn't conflict with other notifications that may be flying around the application.) Make your changeBackgroundColor: method in PreferenceController.m look like this:

- (IBAction)changeBackgroundColor:(id)sender
{
    NSColor *color = [sender color];
    NSData *colorAsData;
    colorAsData = [NSKeyedArchiver archivedDataWithRootObject:color];
    [[NSUserDefaults standardUserDefaults] setObject:colorAsData
                                           forKey:BNRTableBgColorKey];

    NSNotificationCenter *nc;
    nc = [NSNotificationCenter defaultCenter];
    NSLog(@"Sending notification BNRColorChanged");
    [nc postNotificationName:@"BNRColorChanged" object:self];
}

Registering as an Observer

To register as an observer, you must supply several things: the object that is the observer, the names of the notifications in which it is interested, and the message that you want sent when an interesting notification arrives. You can also specify that you are interested only in notifications with a certain object attached to them. (Remember that this is often the object that posted the notification. Thus, when you specify that you want resize notifications with a certain window attached, you are saying that you are interested only in the resizing of that particular window.)

Edit your MyDocument class's init method as follows:

- (id)init
{
    if (self = [super init]) {
        employees = [[NSMutableArray alloc] init];
        NSNotificationCenter *nc;
        nc = [NSNotificationCenter defaultCenter];
        [nc addObserver:self
               selector:@selector(handleColorChange:)
                name:@"BNRColorChanged"
              object:nil];
        NSLog(@"Registered with notification center");
    }
    return self;
}

Unregistering the Observer

Remember to remove your document from the notification center if it is closed. Edit the dealloc method in MyDocument.m as follows:

- (void)dealloc
{
    [self setEmployees:nil];

    NSNotificationCenter *nc;
    nc = [NSNotificationCenter defaultCenter];
    [nc removeObserver:self];
    NSLog(@"Unregistered with notification center: %@",
                                                    [self fileName]);
    [super dealloc];
}

Handling the Notification When It Arrives

When the notification arrives, the method handleColorChange: is called. Add this method to your MyDocument.m file:

- (void)handleColorChange:(NSNotification *)note
{
    NSLog(@"Received notification: %@", note);
    PreferenceController *sender = [note object];
    NSColor *newColor = [sender tableBgColor];

    // Due to bug in 10.3.0,  this change will not be visible
    // on some systems unless the table view has at least one row.
    [tableView setBackgroundColor:newColor];
    [tableView setNeedsDisplay:YES];
}

Build and run the application. Open several windows and change the preferred background color. Note that all of them receive the notification and change color immediately.

For the More Curious: Delegates and Notifications

If an object has made itself the delegate of some standard Cocoa object, it is probably interested in receiving notifications from that object as well. For example, if you have implemented a delegate to handle the windowShouldClose: delegate method for a window, that same object is likely to be interested in the NSWindowDidResizeNotification from that same window.

If a standard Cocoa object has a delegate and posts notifications, the delegate is automatically registered as an observer for the methods it implements. If you are implementing such a delegate, how would you know what to call the method?

The naming convention is simple: Start with the name of the notification. Remove the “NS” from the beginning and make the first letter lowercase. Remove the “Notification” from the end. Add a colon. For example, to be notified that the window has posted an NSWindowDidResizeNotification, the delegate would implement the following method:

- (void)windowDidResize:(NSNotification *)aNotification

This method will be called automatically after the window resizes. You can also find this method listed in the documentation and header files for the class NSWindow.

For the More Curious: The userInfo Dictionary

If you wanted to include more than just the poster with the notification, you would use the user info dictionary. Every notification has a variable called userInfo that can be attached to an NSDictionary filled with other information that you want to pass to the observers:

NSDictionary *d = [NSDictionary dictionaryWithObject:@"867-5309"
                                              forKey:@"phoneNumber"];
NSNotification *n = [NSNotification notificationWithName:@"BNRFoo"
                                                  object:self
                                                userInfo:d];
[[NSNotificationCenter defaultCenter] postNotification:n];

On the other end, the receiver of the notification can use the data in the userInfo dictionary:

- (void)myMethod:(NSNotification *)note
{
    NSString *thePhoneNumber;
    thePhoneNumber = [[note userInfo] objectForKey:@"phoneNumber"];
}

Just as you defined global variables to hold onto constant strings in Chapter 10 so that the compiler would catch your misspellings, most programmers define global variables for the names of their notifications and the keys in their user info dictionary.

Challenge 1

Make your application beep when it gives up its active status. NSApplication posts an NSApplicationDidResignActiveNotification notification. Your AppController is a delegate of NSApplication. NSBeep() will cause a system beep.

Challenge 2

In PreferenceController, put the new color into the user info dictionary of the BNRColorChanged notification. When MyDocument receives the notification, have it read the new color out of the user info dictionary instead of calling back to the PreferenceController.

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

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