tags:

views:

147

answers:

2

Hi, I'm a newbie in Mac Dev. I come from iPhone dev. My question relates to non-modal windows management. It's quite different from the iPhone and it's memory management model.

Say for example, i have a preference window, i can use something like that to show the window:

-(IBAction)showPreferenceController:(id)sender {
  if (!preferenceController) {
  preferenceController = [[PreferenceController alloc]init];
  }
  [preferenceController showWindow:preferenceController]; 
}

But with this code, the window will stay in memory during app life because the window is never released.

To avoid that, I could also use the method described here :
stackoverflow.com/questions/1391260/who-owns-an-nswindowcontroller-in-standard-practice
Create in PreferenceController a + (id) sharedInstance and release the window using (void)windowWillClose:(NSNotification *)notification

I see many cocoa code samples where non-modal windows are never released. For example here : http://www.mattballdesign.com/blog/2008/10/01/building-a-preferences-window/ : The preference panel and all the subviews are created in awakeFromNib and so will live in memory during all app life.

If you take for example Xcode app, there are a lot of non-modal windows :
- Global Find window (CMD+MAJ+F)
- App Info Panel
- Help Window
- ...

I suppose that these windows are released when they are closed to keep memory as low as possible. I would like some advices to know the best way to manage non-modal windows in a cocoa app. Keep in memory? Releasing asap? I know a mac has a lot of memory compared to an iPhone but I also think it's not good to keep in memory objects we are not using.

Thanks.

Edited with Rob post :

I send -autorelease to the window and set my pointer to nil so I'll recreate the window later. This is similar to the technique you cite, though whether or not to use +sharedController for the Controller isn't related; you can do this whether you have a shared controller or not.

I don't how to do that without a singleton (+sharedController).
I explain what I mean with this example:
In the app Controller :

@interface AppController : NSObject <NSApplicationDelegate> {

Implementation :

-(IBAction)showPreferenceController:(id)sender {  
  if (!preferenceController) {
    preferenceController = [[PreferenceController alloc]init];
  }
  [preferenceController showWindow:preferenceController];
}

In the preferences controller :

@interface PreferenceController : NSWindowController <NSWindowDelegate>

Implementation :

- (void)windowWillClose:(NSNotification *)notification {
  [self autorelease];self=nil;
}

It will crash when i close and reopen after the window : preferenceController is released but not equal to nil. So I need to set preferenceController to nil when the window is closed. There is no problem to do that with a singleton.
Without singleton, I should set appController as the delegate of Preference Window to be able to set preferenceController to nil when the window is closed. But i don't like that way.

Edited with Preston comments
I didn't say it but I want only one instance of my non-modal window even if we call -(IBAction)showPreferenceController:(id)sender several times.
That's why I test if preferenceController is equal to nil or not in appController.
So, I need to set preferenceController to nil in appController if we close the window.
So the solution would be :
In AppController, listening NSWindowWillCloseNotification:

- (void)windowWillClose:(NSNotification *)notification {
    if ([notification object] == [preferenceController window]) {
        [preferenceController autorelease];
        preferenceController = nil;
    }
}

Is it correct? Is this the only one solution? because it seems a little bit complicated just to manage my non modal window...

+3  A: 

You're thinking in all the right ways here. It's not ok to leak memory just because you have a lot of it. That said, it is common not to release windows just because they close on Mac. You should generally hold onto them if you think they're going to be opened again to avoid the cost of reloading them.

There are two schools of thought on NSWindow: owned and unowned. I am of the "owned" school of thought. I generally give every window an owner that retains it with an ivar and releases it when appropriate. Generally that is the delegate, sometimes it's the app controller. In -windowShouldClose:, I send -autorelease to the window and set my pointer to nil so I'll recreate the window later. This is similar to the technique you cite, though whether or not to use +sharedController for the Controller isn't related; you can do this whether you have a shared controller or not.

The unowned school of thought is to use NSWindow's -setReleasedWhenClosed: so that it automatically releases itself when it closes. I believe several of the windows you reference do it this way when they are provided by the system, since there might not be a delegate. This can be useful in certain kinds of panels, but I would be careful with it as a general pattern. It's better to have explicit memory management in almost all cases.

Rob Napier
Thank you Rob, I better understand windows managament. I edited my question following your post because i don't understand how to release the window without singleton object.
Benoit
A: 

If you check "Release When Closed" for your window in Interface Builder or set it programmatically, it will release itself. You can also have the window's delegate autorelease the window in the windowShouldClose: delegate method.

If you want to release the window controller when the window closes, have your window controller listen for its window's NSWindowWillCloseNotification and do a [self autorelease] in that method. I'm not a fan of creating a singleton preferences controller always sticking around that the user will, in practice, rarely interact with.

Preston
Thanks. In Xcode Help, for setReleasedWhenClosed "Release when closed, however, is ignored for windows owned by window controllers.". That's my case. In my question, i put an example with autorelease in "windowWillClose". But the problem is this: I want ONLY ONE window even if we call the window several times. That's why I test if preferencesController is nil or not in the method "showPreferenceController". So, I need to set preferenceController to nil when the window is closed. I must then listen NSWindowWillCloseNotification in appController and not in preferencesController. True or not?
Benoit
I edited my question with your comment in order to find the best solution.
Benoit
Ah, you are correct about that window release behavior.I was referring to a global singleton object, not a usual instance variable. Once allocated, a singleton object isn't released until app termination.Yes, you could have the object that owns your preferences controller do the listening for the window-closing notification and just autorelease the preferences controller there followed by setting the instance variable to nil. The preferences controller will release the window it owns. If memory usage of this thing is that much of a concern for you, that's how I'd do it.
Preston