views:

386

answers:

2

Hi,

In my NSApp delegate I add an observer of an object that is an NSWindow subclass that gets initiated in the delegate itself and that posts a notification once the window gets clicked. The selector is also in the delegate. From that same delegate class I initiate another object which when initiated adds itself as an observer for another window of the same NSWindow subclass of above and the selector is in this newly initiated class too. Both notifications get posted but the problem is that they get posted in both classes... Is this normal? I was hoping that it only got posted once.

@implementation AppController
- (id)init
{
    if (self = [super init])
        [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(toggleTestWindow:) name: @"TestNotification" object: testWindow];
    return self;
}

- (void)toggleTestWindow: (NSNotification *)aNotification
{
    if (!testWindow) {
        testWindow = [[MyWindow alloc] init];
        [mainWindow addChildWindow: testWindow ordered: NSWindowAbove];
    } else {
        [mainWindow removeChildWindow: testWindow];
        [testWindow orderOut: self];
        [testWindow release];
        testWindow = nil;
    }
}
@end
+1  A: 

NSNotifications can be filtered by name and by instance. Either choose different names for each notification, or register each observer with the specific object instance it wants to observe. The selector just tells the notification center what method to call once it has decided that an observer wants a specific notification.

When you register an observer, pass the instance you want to listen to as the object parameter. When you post the notification from that instance, pass self as the object.

drawnonward
That's exactly what I'm doing (apart from filtering it by name). Both observers are registered with two different IBOutlets which are at first nil but when the notification gets posted (not necessarily when the window gets clicked) then the selector initiates them and deallocates them later when the NSNotification gets posted again...I'm doing it this way so I can release the IBOutlets whenever the notification gets posted without knowing which IBOutlet the window is from inside itself but I do know it from within the selector where I can release it.
Jan Hendrix
Jan Hendrix: IBOutlets are just variables. When you say `object:nameOfVariable`, you are not passing the variable, you are passing the object pointer that's in the variable. If that pointer is `nil`, then it is no different from saying `object:nil`. You need to have the window in that variable *before* you pass it to the `addObserver:selector:name:object:` method.
Peter Hosey
Peter Hosey: Do you mean that I'm not supposed to allocate/initiate/deallocate the IBOutlet its pointer inside the selector?
Jan Hendrix
Jan Hendrix: That doesn't make any sense. Selectors are names of methods. I think you should edit the delegate class's implementation into your question so we can see what you're doing.
Peter Hosey
Peter Hosey: Edited. Note: I did make mistake telling you it was about an IBOutlet but actually it's just an instance variable.
Jan Hendrix
A: 

I'm right about what I said in my comment on drawnonward's answer.

Variables are containers. A variable is distinct from the value that's in it. Generally, when you use the name of a variable in your code, you're actually referring to the value; when you say foo(bar), you're not passing the bar variable itself to the foo function, you're passing the value that's in the bar variable.

Local variables aren't initialized to anything unless you initialize them. So, don't ever refer to a local variable without either assigning to it or initializing it previously. Random bad things will randomly happen.

Instance variables, on the other hand, are initialized to nil, and will continue to contain nil until you put something else in them. This matters because all through init, you haven't put anything in the testWindow instance variable, so it contains nil.

Then, by saying addObserver:… selector:… name:… object:testWindow, you pass that default value, nil, as the object for which you want to observe for notifications. That translates to observing for that notification for any object.

That isn't what you meant, but what you meant isn't what you wrote. What you meant is to add yourself as an observer for the test window. But you haven't created the test window yet, and you haven't put its pointer in the testWindow variable, so what you wrote is to add yourself as an observer for any object.

Only when the notification happens do you create the window (incorrectly, at that) and assign it to the variable. This is too late for it to have any effect on your observation; the assignment does not retroactively change how you're observing, because you could only pass what was in the variable at that time (which was nil); you could not and cannot pass the variable nor any possible future values of the variable.

So, you need to create the window and assign to the variable in init, then add yourself as an observer for the notification.

There are two correct ways to create a window in code. This is one of them, and this is the other. Don't use plain init to create a window, because it will have no frame rectangle.

Or, better yet, instead of doing all this in code, just use IB to make the window. You'll need to make testWindow an outlet and start observing in awakeFromNib.

Either way, you have a problem on the other end, too, because you release (and thereby destroy, or at least attempt to destroy) the window in your notification method. Don't expect to continue receiving notifications for an object after you destroy it. You need to move that release message and nil assignment out to someplace else in your code, to a place where you're truly finished with the window and not just hiding it temporarily.

In summary:

  1. Create the window and assign its pointer to the testWindow variable in init, before you send that addObserver:selector:name:object: message.
  2. For a window that the user can show and hide, keep the window's lifetime separate from its shown/hidden state. It's OK to have a window object that's ordered out; you don't need to destroy it as soon as you order it out. Memory isn't that scarce anymore—not on the Mac, anyway. Release the window only when you are truly done with it—probably just in dealloc.

(Oh, and a style/maintanability matter: Don't sprinkle literal strings like @"TestNotification" all over your code. Define a variable with that value somewhere, and use that variable everywhere you want to use the notification. Then, to change the string, you change it in exactly one place, and to rename the variable, you can use Xcode's Refactor tool.)

Peter Hosey
Thanks for all that. I actually don't init my window like that but I just didn't add the whole NSMakeRange() part.
Jan Hendrix
Er, you mean `NSMakeRect`?
Peter Hosey