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...
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.
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.
Related
I'm having trouble with the animation of some subclassed indeterminate NSProgressIndicators. They start and stop animating without any issues. However, if I minimise the window while animating, stopAnimation: / StopAnimation(NSObject sender) is called, which makes sense to save resources if the window is not visible. I assume this is invoked from the cocoa framework itself looking at the stacktrace.
The problem then arises when the window is restored, the animation is not resumed.
I've seen you can use the NSCoding Protocol and can override encodeWithEncoder: / EncodeTo(NSCoder encoder) to save some state, and then use that saved state in initWithCoder: / AppProgressIndicatorBar(NSCoder coder) to resume. But the problem here was that my encodeWithEncoder: / EncodeTo(NSCoder encoder) was never called.
Looking at this SO question and answer, it should be handled automatically if the object needs to be serialized. So I'm not sure why it's not being called.
That same answer says you can do it explicitly with NSKeyedArchiver, but then I would need to listen with NSWindowDelegate to know when the window is minimizing / restoring. In which case, I could just use this, and not use the NSCoding Protocol...
This just feels dirty, and I would imagine this is a very common scenario. So how should / do you resume animation? I'm new to cocoa, coming from a mostly .NET background, and I think this problem is a symptom of my limited cocoa knowledge.
I'm using Xamarin Mac, and have tried to give the Objective-C and C# method signatures. I'll be happy for a solution in either, I'll be able to (hopefully!) convert it to the C# equivalent.
For completeness, here is my current Xamarin Mac subclass using the NSCoder Protocol where EncodeTo is not being called. I'm running OS X 10.11.3 and Xamarin Studio 5.10.2.
[Register("AppProgressIndicatorBar")]
public class AppProgressIndicatorBar : NSProgressIndicator, INSCoding
{
...
public AppProgressIndicatorBar(NSCoder coder) : base(coder)
{
...
}
...
public override void EncodeTo(NSCoder encoder)
{
base.EncodeTo(encoder);
...
}
...
}
You should be able to use the NSWindowWillMiniaturizeNotification, NSWindowDidMiniaturizeNotification and NSWindowDidDeminiaturizeNotification notifications or the windowWillMiniaturize:, windowDidMiniaturize: and windowDidDeminiaturize: Window delegate methods to track the state of your window and restore the state of your progress bar when the window deminiaturises (is that really a word?).
HTH
I'm working with an NSWindowController to implement a preferences window. Apple's documentation states that by default the controller and window aren't deallocated, because it's useful to not have to reload everything, which makes sense. But their documentation goes on to say that you can override that behavior, but not explain how.
Apple's Docs:
When a window is closed and it is part of a document-based
application, the document removes the window’s window
controller from its list of window controllers. This results
in the system deallocating the window controller and the
window, and possibly the NSDocument object itself. When a
window controller is not part of a document-based application,
closing the window does not by default result in the
deallocation of the window or window controller. This is the
desired behavior for a window controller that manages something
like an inspector; you shouldn’t have to load the nib file
again and re-create the objects the next time the user requests
the inspector.
If you want the closing of a window to make both
window and window controller go away when it isn’t
part of a document, your subclass of NSWindowController
can observe the NSWindowWillCloseNotification notification
or, as the window delegate, implement the windowWillClose: method.
I can't find anywhere that explains what to "implement" in the windowWillClose: method.
The window controller can be seen here:
https://github.com/gngrwzrd/gwpreferences/blob/master/GWPreferences/GWPreferences/GWPreferences/GWPrefsWindowController.m
Using the controller can be seen here:
https://github.com/gngrwzrd/gwpreferences/blob/master/GWPreferences/GWPreferences/GWAppDelegate.m - you can see in this code where I'm trying some bridge casting to try and force release objects but it doesn't work.
So the GWPrefsWindowController.dealloc method never gets called. Any ideas?
I understand this question is old, but for those who came here from google, the answer is quite simple.
As stated in the documentation, for non document base applications, you can simply:
Keep a reference for your NSWindowController wherever your are calling it. (In the example below it's referenced by myWindowController;
Make the class calling your NSWindowController implement the protocol NSWindowDelegate;
Release your Window Controller by setting it to nil on windowWillClose: method
To answer the question more precisely. When lazy instantiating your controller, set your class as the delegate:
-(IBAction)showMyWindowAction:(id)sender
{
// If my window controller is not nil
if (!myWindowController)
{
//instantiate it
myWindowController = [[MyWindowController alloc] initWithWindowNibName:#"myWindow"];
// set your class as delegate
[myWindowController setDelegate:self];
}
[myWindowController.window orderFront:self];
}
And then implement the windowWillClose: method from the NSWindowDelegate protocol
-(void)windowWillClose:(NSNotification *)notification
{
//Check if it's the right window that will close
if ([notification.object isEqualTo:myWindowController.window])
{
//Set your controller to nil
myWindowController = nil;
}
}
That's it, your window controller will now dealloc and since we are verifying if it's controller is nil before showing the window, everything will work!
I believe the reason why this is not implemented by default is because the initWithWindowNibName: is a somewhat heavy operation, and thus you have to think if dealloc'ing whatever is on your window will impact more or less than loading your window nib file.
I hope it helped
I would like to track each time a certain window appears (becomes visible to the user) in a OS X app. Where would be the most adequate place to call the tracker?
windowWillLoad, maybe?
I expected to find something like windowWillAppear but it seems I'm thinking too much iOS.
How about getting notification such as NSWindowDidBecomeMainNotification, By main I guess the one which is top most on screen directly visible by user.
see : Apple Documentation
Yes, one would expect that a window would notify its delegate or its controller with a windowWillAppear or windowDidAppear message, or post a documented notification like NSWindowDidAppearNotification. But alas, none of those exist. I filed a bug report with Apple and was given the advice to use a storyboard and a view controller instead. This is unhelpful in legacy apps that already use a bunch of window controllers and xibs.
You could subclass NSWindow and override orderWindow:relativeTo: to send a notification. Most, but not quite all, of the messages that make a window show itself ultimately go through this method, including orderBack:, orderFront:, makeKeyAndOrderFront:, and -[NSWindowController showWindow:]. But orderFrontRegardless does not go through orderWindow:relativeTo:, so you would also want to override that for completeness.
Another way to be notified is to make a subclass of NSViewController that controls some view that's always visible in the window. The view controller will receive viewWillAppear and viewDidAppear.
If you're subclassing NSWindow or NSViewController already for some other reason, either of these is a reasonable solution.
If you're not subclassing NSWindow already, and don't have an NSViewController subclass for a view that's always visible in the window, then another way is to use Cocoa bindings to connect the window's visible binding to a property one of your objects. For example, I have a custom NSWindowController subclass. I gave it a windowIsVisible property:
#interface MyWindowController ()
#property (nonatomic) BOOL windowIsVisible;
#end
and I implemented the accessors like this:
- (BOOL)windowIsVisible { return self.window.visible; }
- (void)setWindowIsVisible:(BOOL)windowIsVisible {
NSLog(#"window %# became %s", self.window, windowIsVisible ? "visible" : "hidden");
}
and in awakeFromNib, I bind the window's visible binding to the property like this:
- (void)awakeFromNib {
[super awakeFromNib];
[self.window bind:NSVisibleBinding toObject:self withKeyPath:NSStringFromSelector(#selector(windowIsVisible)) options:nil];
}
When the window becomes visible, the setWindowIsVisible: setter is called with an argument of YES. Note that if the whole app is hidden and reappears, the setter is called again, even though it wasn't called with argument NO when the app was hidden. So be careful not to assume the window was previously hidden.
Also, the binding might create a retain cycle, so you should probably unbind it when the window is closed, unless you want to keep the window and controller around. Note that the window does post NSWindowWillCloseNotification when it's closing, so you don't need any special magic to detect that.
How can I load a nib inside of another window?
I tried initWithWindowName,
if (mmController == NULL)
mmController = [[mainMenu alloc] initWithWindowNibName:#"mainMenu"];
[mmController showWindow:self];
but it opens a new window.
I also tried loadNibNamed
[NSBundle loadNibNamed:#"mainGame" owner:self];
and it succeeded, but when I try to use the same method to get back to the main menu,
[NSBundle loadNibNamed:#"mainMenu" owner:self];
it doesn't work. It does nothing at all...
Any ideas?
I tried initWithWindowName,
You mean initWithWindow¹Nib²Name³:, which takes the name (3) of a nib (2) containing a window (1).
if (mmController == NULL)
This should be nil, not NULL, since you are comparing an Objective-C object pointer.
mmController = [[mainMenu alloc] initWithWindowNibName:#"mainMenu"];
What is mainMenu here? It must be a class, but what is it a subclass of?
[mmController showWindow:self];
From this message and the previous message, I'm guessing mainMenu is a subclass of NSWindowController.
Guessing should not be required. You should name your classes specifically, so that anybody can tell what the class is and its instances are merely by the class name.
Brevity is a virtue, but if you need to go long, go long. We've got modern tools with name completion. The tab key can eliminate the sole advantage of an abbreviated name.
but it opens a new window.
Yes. You created a window by loading it from a nib, and then you told the window controller to show that window. Showing a new window is the expected result.
I also tried loadNibNamed
[NSBundle loadNibNamed:#"mainGame" owner:self];
and it succeeded, but when I try to use the same method to get back to the main menu,
There is no “get back”. Loading a nib is simply creating objects by loading them from an archive. You can load the same nib multiple times, and loading a nib does not somehow undo the results of loading a previous nib.
You may want to read the Resource Programming Guide, which covers nibs as well as image and sound files, and the Bundle Programming Guide.
If you want to hide the window you loaded from the mainGame nib, do that. The term for this in AppKit is “ordering out” (as opposed to “ordering in”, which “ordering front” and “ordering back” are specific ways of doing).
[NSBundle loadNibNamed:#"mainMenu" owner:self];
it doesn't work. It does nothing at all...
Are you trying to load the MainMenu nib that came with your project? If so, make sure you get the case right—you don't want your app to be broken for people who run it from a case-sensitive volume, nor do you want it to be broken for people who use the default case-insensitive file-system.
If that's not what you're trying to do, then it isn't clear what you are trying to do. MainMenu is normally the nib containing the main menu (the contents of the menu bar); naming any other nib “mainMenu” or anything like that is going to cause confusion at best and problems at worst. If this is supposed to be some other nib, you should give it a different name.
Either way, this is not what you need to do. If you want to hide the window you loaded from mainGame, then you need to hide that window, not load a different nib.
Moreover, once the window is loaded, do not load it again (unless you close and release it). Once you have loaded it, you can simply order it back in. Most probably, you will want to both make it key and order it front.
On the Mac, you are not limited to one window at a time; indeed, your app has multiple windows (at least three), no matter what you do. The APIs are built around your ability to show multiple windows.
See the Window Programming Guide for more information.
How can I load a nib inside of another window?
As Justin Meiners already told you, you may want NSViewController for that, although you can go without and just load the nib containing the view directly using loadNibNamed:.
Be warned that NSViewController is not nearly as powerful/featureful as Cocoa Touch's UIViewController.
You'll want to read the View Programming Guide for this.
I have used NSWindowController in projects several times, and feel like I have a (very)rough grasp of the concepts behind this important class. What I would like to do with this post is to clarify/correct my own understandings, and hopefully help other learners get that first step into understanding. It's the at-a-glance concepts, overview, and best practices that I find is most useful, and often lacking in the documentation. Here is my take on NSWindowController (questions are interspersed in bold):
An NSWindowController (NSWC) subclass exists (conceptually) just beneath every window nib, acting as the glue between the user interface elements and the model objects that they control/represent. Basically, every window in your application should have its own NSWC subclass.
The File's Owner of the nib should always be the NSWC subclass. Is this the case even for the MainMenu.xib application?
The NSWC window property should always be linked to the NSWindow in InterfaceBuilder.
You should override the 'init' method, using [super initWithWindowNibName:], so that when you refer to [mycontroller window] it will load the nib. Should this also be the case for the NSWC for the MainMenu.xib window, even though this is opened at startup?
The NSWC shouldn't do too much heavy lifting - it should simply pass messages to instances of objects, and present those objects in the UI.
It can modify the UI using binding, or acting as a delegate for tables etc., or by actively changing the UI elements when it observes a change, or a combo of any of the above (which one you use seems to be a matter of taste, with pros and cons on all sides).
An NSWC can create instances of other NSWCs when necessary (for example, when opening a one-off sub-window).
Use the [mycontroller showWindow:nil] to display the associated window at the front. If you want the window to appear as a sheet, use something like:
NSWindowController* mycontroller = [[MyController alloc] init];
[NSApp beginSheet: [mycontroller window]
modalForWindow: [self window]
modalDelegate: self
didEndSelector: #selector(didEndMySheet:returnCode:contextInfo:)
contextInfo: nil];
The didEndSelector: should be a method of the NSWC of the parent window, and can access and release 'mycontroller' with [sheet windowController].
- To close the window call the performClose: method of NSWC's window.
Some Questions:
Should the NSWC of the MainMenu window also be the application delegate, or should this be a different class?
In the same vein, should the main NSWC handle files (drag/drop and opening), or should it be passed on to the app delegate, or is that just a matter of taste?
Please correct me if any of this is bad practice, or is just plain wrong. I am looking to clarify my understanding of NSWindowController, so any additions (in the form of best practices, experiences, gotchas) would be highly appreciated.
Thanks,
Laurie
What are window controllers actually for?
Window controllers are tools to load a window from a NIB file and for managing the memory of the resources allocated in the NIB. Before there where NSWindowControllers one basically had to write the same code for every window or invent an own window controller class.
Of course they are also controllers in the Model/View/Controller sense, so they are the right place to connect the views from the window to the model objects. To do this they often need to act as the delegate or data source for a view object. So you got this part perfectly right.
Also window controllers are a tool for code reuse. It makes it easy to drop the window controller class and it’s XIB/NIB into another project and use it there.
So yes, every window from a NIB should be owned by a window controller, with one exception. Actually, this is just a guideline for good code, nothing enforces it.
WindowControllers and MainMenu.xib
MainMenu.xib is a different thing, there you can’t use a window controller. This NIB gets loaded by NSApplication so this has to be it’s "Files owner". There is no way to get a window controller between the NSApplication and the NIB. It also isn’t necessary to use a window controller for memory management there, since the application object lives for the entire runtime of the program, so it doesn’t have to clean up it’s resources from the NIB when it gets deallocated.
If you really need a window controller for your main window you cannot put this in the MainMenu.xib.
I hope this helps. There probably is a lot more to say about window controllers too
Is this the case even for the MainMenu.xib application?
No, the MainMenu nib is owned by NSApplication (that's who loads it).
Should this also be the case for the NSWC for the MainMenu.xib window, even though this is opened at startup?
No, NSApplication loads the main nib based on your applications file's "NSMainNibFile" property. (It just happens to be pre-set to "MainMenu" in the template Xcode projects.) If you want to change its name then change it there (and rename your nib file). (BTW: This property can also be changed in your target's "Summary" view in Xcode 4.)
Should the NSWC of the MainMenu window also be the application delegate, or should this be a different class?
The owner of the NSMainNibFile nib is the instance of NSApplication that loads it and by association any delegate of that instance. Neither of these are NSWC sub-classes.
In the same vein, should the main NSWC handle files (drag/drop and opening), or should it be passed on to the app delegate, or is that just a matter of taste?
There is no "main NSWC" (The app/app-delegate is the controller for the NSMainNibFile).
All drag-n-drop operations are handled by NSWindow or NSView sub-classes. I usually use a special NSWindow or NSView sub-class that just passes all drag-n-drop methods thru to the delegate. For example:
- (unsigned int) draggingEntered:sender
{
return [[self delegate] draggingEntered:sender];
}
This way I can keep all my window/view code together in their respective controller (as determined by their nib owner). And because the window/view specific code is in the controller (not the NSWindow/NSView subclass) different types of NSWindows/NSViews can all use the same drag-n-drop sub-classes.