EXC_BAD_ACCESS when closing modal window ([NSWindow _restoreLevelAfterRunningModal]: message sent to deallocated instance) - objective-c

In my main ViewController I have the following code:
- (IBAction)listFunctions:(id)sender //button clicked
{
FunctionListController *functionListController = [[FunctionListController alloc] initWithWindowNibName:#"FunctionList"];
NSWindow *functionListWindow = [functionListController window];
[NSApp runModalForWindow: functionListWindow];
NSLog(#"done");
}
FunctionListController is the File's Owner of FunctionList.nib and a subclass of NSWindowController and implements the protocol NSWindowDelegate.
Here is the implementation of FunctionListController:
#implementation FunctionListController
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if(self)
{
// Initialization code here.
}
return self;
}
- (void)windowDidLoad
{
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
self.window.delegate = self;
}
- (void)windowWillClose:(NSNotification *)notification
{
[NSApp stopModal];
}
#end
When the modal window is closed, the NSLog(#"done"); runs and displays, however after listFunctions is done, I get a EXC_BAD_ACCESS error.
With NSZombiesEnabled I get the error [NSWindow _restoreLevelAfterRunningModal]: message sent to deallocated instance.
Edit:
I am using ARC.

Try [functionListWindow setReleasedWhenClosed:NO] and hold a strong reference to your window until closing.

In your listFunctions method, you first create a FunctionListController object:
- (IBAction)listFunctions:(id)sender //button clicked
{
FunctionListController *functionListController = [[FunctionListController alloc] initWithWindowNibName:#"FunctionList"];
which is referenced through a local variable; it will be released at the end of the scope (the method itself);
you then get a reference to the functionListController window and run it as a modal:
NSWindow *functionListWindow = [functionListController window];
[NSApp runModalForWindow: functionListWindow];
This object will be retained by the NSApp.
However, the method exits (runModalForWindow will not block your thread) and functionListController is deallocated:
NSLog(#"done");
}
so you get a dangling reference and a modal window owned by an object which is not longer there. Hence, then crash.
Simply, make functionListController a strong property of your class and it will work.
Your new listFunctions would look like:
- (IBAction)listFunctions:(id)sender //button clicked
{
self.functionListController = [[FunctionListController alloc] initWithWindowNibName:#"FunctionList"];
...

Your functionListWindow is a local variable in your listFunctions: method. When that method finishes executing, you will lose any strong reference you have to that object, so nothing will own it and it will be deallocated. When your modal window actually closes, it tries to send the appropriate message to its delegate, however this no longer exists.
Have you tried making functionListWindow an instance variable on your main view controller?

Related

Should I need to unbind cocoa-bindings in dealloc of windowController?

I have window controller and view controller that use core data bindings, but I want to be able to have those views really, truly get deallocated. I then want to reset the managedObjectContext and at that point have given up as much memory as possible.
I have found that I am required to unbind things which I've bound in the Nib, or the MOC keeps the Nib objects retained.
The issue is this EXEC_BAD_ACCESS trace:
If I do not unbind all of the bindings, even those created in Interface Builder, reset on the MOC causes an EXEC_BAD_ACCESS because bindings are attempting to reflect changes in the MOC in the view that's gone, through an array controller that should be gone, but isn't.
So I did this in the window controller's dealloc:
- (void) dealloc
{
NSLog(#"Wincon dealloc");
#autoreleasepool {
// Remove subview to ensure subview dealloc
[_viewController.view removeFromSuperviewWithoutNeedingDisplay];
// Tear down bindings to ensure MOC can reset
for (NSObject<NSKeyValueBindingCreation>* object in
#[_watcherAC, _watchersTimesTreeController, _watcherTableView, _itemsOutlineView])
{
for (NSString* binding in [object exposedBindings])
[object unbind:binding];
}
}
}
which is triggered this way:
- (void) switchToBackgroundMode
{
NSLog(#"SwitchToBackgroundMode");
// Hide the menu and dock icon
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
// Force every view to deallocate before reset
#autoreleasepool {
// Need to check loaded to prevent closing a closed window and
// triggering a second call to applicationShouldTerminateAfterLastWindowClosed
if ([self.wincon isWindowLoaded]) [self.wincon close];
self.wincon = nil;
}
NSLog(#"About to resetCoreDataStack");
[self resetCoreDataStack];
}
... and now I don't get any errors with that resetCoreDataStack
The stack trace above comes with a log file like this:
2014-05-29 15:54:35.794 MyApp[10230:303] Switch to BG in appShouldTerminate
2014-05-29 15:54:35.794 MyApp[10230:303] SwitchToBackgroundMode
2014-05-29 15:54:35.808 MyApp[10230:303] Wincon dealloc
2014-05-29 15:54:35.830 MyApp[10230:303] About to resetCoreDataStack
2014-05-29 15:54:35.830 MyApp[10230:303] Reset Core Data
{Exception thrown iff wincon dealloc doesn't unbind everything}
And so the window controller dealloc is definitely called when it's nilled in the autoreleasepool, but MOC reset causes an EXEC_BAD_ACCESS unless that wincon dealloc does unbind on a bunch of crap in the Nib.
So the question is:
Given a Nib owned by a custom window controller (self.wincon) with arrayController objects bound to an external managedObjectContext, what needs to be done to force everything in the Nib to be released and unbound? Is there some step that I'm missing that causes me to have to do this unbinding manually?
[EDIT] Some new debug code:
NSLog(#"Wincon dealloc");
#autoreleasepool {
// Remove subview to ensure subview dealloc
[_viewController.view removeFromSuperviewWithoutNeedingDisplay];
_viewController = nil;
self.window = nil;
}
#autoreleasepool {
// Tear down bindings to ensure MOC can reset
for (NSObject<NSKeyValueBindingCreation>* object in
#[_watcherAC, _watchersTimesTreeController, _watcherTableView, /*_itemsOutlineView*/])
{
NSLog(#"Bindings for %#", [object className]);
for (NSString* binding in [object exposedBindings]) {
NSLog(#"BI for %#: %#", binding, [object infoForBinding:binding]);
[object unbind:binding];
}
}
the log below is the bindingInfo for the bindings still alive when dealloc is called for the windowController
2014-05-29 21:00:39.967 SaleWatch[11249:303] Wincon dealloc
2014-05-29 21:00:39.975 SaleWatch[11249:303] Bindings for NSArrayController
2014-05-29 21:00:39.978 SaleWatch[11249:303] Bindings for NSTreeController
2014-05-29 21:00:39.989 SaleWatch[11249:303] BI for contentSet: {
NSObservedKeyPath = "selection.fetchTimesForOutlineView";
NSObservedObject = "[entity: SWWebStoreWatcher, number of selected objects: 1]";
}
2014-05-29 21:00:39.991 SaleWatch[11249:303] Bindings for NSTableView
2014-05-29 21:00:39.991 SaleWatch[11249:303] BI for selectionIndexes: {
NSObservedKeyPath = selectionIndexes;
NSObservedObject = "[entity: SWWebStoreWatcher, number of selected objects: 1]";
}
2014-05-29 21:00:40.001 SaleWatch[11249:303] BI for content: {
NSObservedKeyPath = arrangedObjects;
NSObservedObject = "[entity: SWWebStoreWatcher, number of selected objects: 1]";
}
2014-05-29 21:00:40.020 SaleWatch[11249:303] About to resetCoreDataStack
I forced wincon.window = nil in the new code, and these three objects still aren't nil, though the outlineView the treeController is for did become nil. There could be a retain cycle here, but I don't see how it'd be my fault... yet.

Presenting modal dialogs from XIB in Cocoa: best/shortest pattern?

Below is my typical WindowController module for presenting a modal dialog (could be settings, asking username/password, etc) loaded from a XIB. It seems a bit too complex for something like this. Any ideas how this can be done better/with less code?
Never mind that it's asking for a password, it could be anything. What frustrates me most is that I repeat the same pattern in each and every of my XIB-based modal window modules. Which of course means I could define a custom window controller class, but before doing that I need to make sure this is really the best way of doing things.
#import "MyPasswordWindowController.h"
static MyPasswordWindowController* windowController;
#interface MyPasswordWindowController ()
#property (weak) IBOutlet NSSecureTextField *passwordField;
#end
#implementation MyPasswordWindowController
{
NSInteger _dialogCode;
}
- (id)init
{
return [super initWithWindowNibName:#"MyPassword"];
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self.window center];
}
- (void)windowWillClose:(NSNotification*)notification
{
[NSApp stopModalWithCode:_dialogCode];
_dialogCode = 0;
}
- (IBAction)okButtonAction:(NSButton *)sender
{
_dialogCode = 1;
[self.window close];
}
- (IBAction)cancelButtonAction:(NSButton *)sender
{
[self.window close];
}
+ (NSString*)run
{
if (!windowController)
windowController = [MyPasswordWindowController new];
[windowController loadWindow];
windowController.passwordField.stringValue = #"";
if ([NSApp runModalForWindow:windowController.window])
return windowController.passwordField.stringValue;
return nil;
}
The application calls [MyPasswordWindowController run], so from the point of view of the user of this module it looks simple, but not so much when you look inside.
Set tags on your buttons to distinguish them. Have them both target the same action method:
- (IBAction) buttonAction:(NSButton*)sender
{
[NSApp stopModalWithCode:[sender tag]];
[self.window close];
}
Get rid of your _dialogCode instance variable and -windowWillClose: method.
-[NSApplication runModalForWindow:] will already center the window, so you can get rid of your -awakeFromNib method.
Get rid of the invocation of -[NSWindowController loadWindow]. That's an override point. You're not supposed to call it. The documentation is clear on that point. It will be called automatically when you request the window controller's -window.
Get rid of the static instance of MyPasswordWindowController. Just allocate a new one each time. There's no point in keeping the old one around and it can be troublesome to reuse windows.

Can't close a window- why?

I have an application in which a window should be opened and closed when a checkbox is clicked on or off in a separate window. I can open it, but can't close it. I define a NSWindow in the windowControllerObject and try to close the NSWindow. The relevant code is:
buttonController.h
#interface buttonController : NSWindowController
{
NSButton *showAnswerBox;
infoWindowController *answerWindowController;
}
- (IBAction)showAnswer:(id)sender;
#end
buttonController.m
- (IBAction) showAnswer:(id) sender
{
if ([sender state] == NSOnState) {
if (!answerWindowController) {
answerWindowController = [[infoWindowController alloc] init];
}
[answerWindowController showWindow:self];
}
else {
[answerWindowController hideWindow];
}
}
infoWindowController.h:
#interface infoWindowController : NSWindowController {
IBOutlet NSWindow * infoWindow;
}
- (id) init;
- (NSWindow *) window;
- (void) hideWindow;
- (void) tsSetTitle: (NSString *) displayName;
#end
And in infoWindowController.m:
- (NSWindow *) window
{
return infoWindow;
}
- (void) hideWindow
{
[[self window] close];
}
The window opens, but it won't close. I've tried several variations, including orderOut on the infoWindowController. I'm sure I'm missing something dumb- what is it?
In IB, the only way I can even get the windows to open is if 'Open at launch' checked- shouldn't I be able to open them programmatically without that?
NSWindowController already defines a window property. You have effectively overridden the getter of that property by implementing your own -window method. The setter, though, is still the inherited version.
So, assuming you have connected the window outlet of the controller to the window in the NIB, the inherited setter is being called. That allows the inherited implementation of -showWindow: to work to show the window. But your -window method will return nil because the inherited setter does not set your infoWindow instance variable.
Get rid of your separate infoWindow property and getter. Just use the inherited window property and its accessors.
If you use NSWindowController it's better to use it's close method:
- (void) hideWindow
{
[self close];
}
or just:
[answerWindowController close];
But your code is also valid, just make sure that your [answerWindowController window] is not nil. If you load your window from xib you should initialize your window controller with the name of this xib: answerWindowController = [[AnswerWindowControllerClass alloc] initWithWindowNibName:#"YOUR WINDOW XIB NAME"];.
Also check that "Visible at launch" is unchecked for your window (it seems that it doesn't).

Memory managment question

I have a button with IBAction, which shows another window:
-(IBAction)someButtonClick:(id)sender
{
anotherView = [[NSWindowController alloc] initWithWindowNibName:#"AnotherWindow"];
[anotherView showWindow:self];
}
I worry about memory management in here. I allocate an object in this IBAction and don't released it. But how can i do it? If i released this object after showing, window will closing immediately.
The view is stored in an instance variable and you have access to it anywhere in your class. Release it in the code that dismisses the view.
Since anotherView is an instance variable you can release it in your dealloc method. But then you still have a memory leak, since every time your button is clicked a new instance of the window controller is created, but only the last one can be freed. You really should use accessors for this. Here is my suggestion:
- (NSWindowController *) anotherView;
{
if (nil == anotherView) {
anotherView = [[NSWindowController alloc] initWithWindowNibName:#"AnotherWindow"];
}
return anotherView;
}
- (void) setAnotherView: (NSWindowController *) newAnotherView;
{
if (newAnotherView != anotherView) {
[anotherView release];
anotherView = [newAnotherView retain];
}
}
- (void) dealloc;
{
[self setAnotherView: nil];
[super dealloc];
}
- (IBAction) someButtonClick: (id) sender;
{
[[self anotherView] showWindow: self];
}
If you use a Objective-C 2.0 property you don't have to write the setter.
And also you should rename your instance variable, the name should reflect what it is. And a View is not a Window Controller.

How do you implement the Method makeKeyAndOrderFront:?

I am making a new window open and would like to implement the method makeKeyAndOrderFront: for the window, i was wondering what code i would need to enter to do this.
Here is some of the code I've already got to open the window:
File 1 (The First Controller)
#import "PreferenceController.h"
#implementation PreferenceController
- (id)init
{
if (![super initWithWindowNibName:#"Preferences"])
return nil;
return self;
}
- (void)windowDidLoad
{
NSLog(#"Nib file is loaded");
}
File 2 (The Action Opening The Window)
#import "Prefernces_Delegate.h"
#import "PreferenceController.h"
#implementation Prefernces_Delegate
- (IBAction)showPreferencePanel:(id)sender
{
// Is preferenceController nil?
if (!preferenceController) {
preferenceController = [[PreferenceController alloc] init];
}
NSLog(#"showing %#", preferenceController);
[preferenceController showWindow:self];
}
The reason I am trying to do this is it has been suggested by a friend to solve a window opening problem.
You don't want to implement -makeKeyAndOrderFront:, you want to call it on your window in order to bring it to front and make it the key window. What does your showWindow: method do?
Somewhere after [preferenceController showWindow:self];:
[self.window makeKeyAndOrderFront:self];
or did you mean add a method to the controller?
// you should use a different method name, cause it's not the
// controller that is made key and ordered front.
- (void)makeKeyAndOrderFront:(id)IBAction {
[self.window makeKeyAndOrderFront:self];
}
Message makeKeyAndOrderFront is sent only after initiating the main event loop [NSApp run]. You may try to send the message from the main view by implementing the method viewWillDraw:
- (void)viewWillDraw
{
[window makeKeyAndOrderFront: nil];
}