NSDocument, Multiple Views, and setShouldCloseDocument
-
Problem with NSDocument, Multiple Views, and setShouldCloseDocument
I am having a slight problem using the document architecture with
multiple views and the application aborting when quitting. Hopefully
someone can tell me the right way to do this.
The short version of the problem is that when I have my main document
window open and an alternate window for that same document open and then
quit the application, the application aborts with a signal 11
(SIGSEGV) [ under slightly different but similar scenarios a signal 10
(SIGBUS). ]
The problem (I think) is that I am over autoreleasing. I can't figure
out the right combination of autoreleasing that works in all cases
(without leaking during normal application usage). If I don't do it the
way that I do below, then I leak when just closing windows. I have
tried other variants with similar problems. If I remove the [ self
autorelease ] from the ALT window controller (see below), then it
doesn't abort, but it doesn't release itself during normal window
closing.
I have created a simple sample below to reproduce the problem.
[ 2nd very related question: Why does my main window controller
automatically release itself when closed, but my Alt window controller
needs and autorelease in the windowWillClose? ]
Any help, advice, etc?
Thanks in advance,
Bill Keirstead
Here is the output:
[ Opened MainWindow and then alternate window on top and selected
file->quit ]
AltWindowController::windowWillClose
AltWindowController::windowWillClose
MyDocument::dealloc
AltWindowController::dealloc
testDoc.app has exited due to signal 11 (SIGSEGV).
Here are the relevant snippets:
====================================
====================================
@implementation MyDocument
- (void) makeWindowControllers {
m_pController = [ [ MainWindowController allocWithZone: [ self
zone ] ] init ];
[ self addWindowController: m_pController ];
}
***snipped standard stuff***
- (void)dealloc {
printf( "MyDocument::dealloc\n" );
[ m_pController autorelease ];
[ super dealloc ];
}
====================================
====================================
@implementation MainWindowController
- (id)init {
self = [ super initWithWindowNibName: @"MyDocument" ];
return self;
}
- (void) windowDidLoad {
[ self setShouldCloseDocument: YES ];
}
- (IBAction)open2ndWindow:(id)sender
{
AltWindowController *pWindowController =
[ [ AltWindowController allocWithZone: [ [ self document ]
zone ] ] init ];
[ [ self document ] addWindowController: pWindowController ];
}
- (void)dealloc {
printf( "MainWindowController::dealloc\n" );
[ super dealloc ];
}
@end
====================================
====================================
@implementation AltWindowController
- (id)init {
self = [ super initWithWindowNibName: @"AltWindowController" ];
[ [ super window ] makeKeyAndOrderFront: self ];
return self;
}
- (void)dealloc {
printf( "AltWindowController::dealloc\n" );
[ super dealloc ];
}
- (void) windowWillClose:(id) sender
{
printf( "AltWindowController::windowWillClose\n" );
[ self autorelease ];
}
@end -
Bill Keirstead wrote:
>[snip]
> Problem with NSDocument, Multiple Views, and setShouldCloseDocument
>
> - (void) windowWillClose:(id) sender
> {
> printf( "AltWindowController::windowWillClose\n" );
> [ self autorelease ];
> }
I suspect that your program is segfaulting because something's sending
your AltWindowController instance a -windowDidClose: message after
you've already freed it.
What happens if you move this code to a -windowDidClose: method, instead?
-jcr
"The right to be heard does not include the right to be taken
seriously." - Hubert Humphrey -
It is very possible that the fault is mine, but I don't see any
documentation or references in the header files for windowDidClose.
Does this actually exist?
- Bill
On Tuesday, April 17, 2001, at 08:35 PM, John C. Randolph wrote:
>
> Bill Keirstead wrote:
>>
>> Problem with NSDocument, Multiple Views, and setShouldCloseDocument
>>
> [snip]
>
>> - (void) windowWillClose:(id) sender
>> {
>> printf( "AltWindowController::windowWillClose\n" );
>> [ self autorelease ];
>> }
>
> I suspect that your program is segfaulting because something's sending
> your AltWindowController instance a -windowDidClose: message after
> you've already freed it.
>
> What happens if you move this code to a -windowDidClose: method,
> instead?
>
> -jcr
>
> "The right to be heard does not include the right to be taken
> seriously." - Hubert Humphrey
> _______________________________________________
> MacOSX-dev mailing list
> <MacOSX-dev...>
> http://www.omnigroup.com/mailman/listinfo/macosx-dev
>
-
On Tuesday, April 17, 2001, at 06:31 PM, Bill Keirstead wrote:
> It is very possible that the fault is mine, but I don't see any
> documentation or references in the header files for windowDidClose.
> Does this actually exist?
Hmm. I thought that the -windowWill... and -windowDid.. delegate
methods included a -windowDidClose, but apparently not.
At any rate, the symptom you described still sounds to me like a message
being sent to a freed object.
-jcr
>
For every complex problem there is an answer that is clear, simple, and
wrong. -- H L Mencken -
(I'm replying from the archives since I deleted the original e-mail)
Well, I implemented the sample code you posted and stepped through it
with the debugger and I messed around with the code and here's what I
discovered. (I also fixed the retain/release slip in
MainWindowController in open2ndWindow: -- pWindowController should be
released -- but it didn't change the behavior).
The reason for the crashes is that when the application quits, it is
using makeObjectsPerformSelector to close all the windows that are open.
So the main document window gets a call to close from NSApp, since
shouldCloseDocument is YES, it tells the document to close. That closes
the sub-window, then the sub-window is closed again by the call from
NSApp. (So as John Randolph suspected, it is getting two free messages.)
I'm not sure if there's a proper way to fix this or not. From an
ugly-hack standpoint, you could register the main window controller for
application termination messages and setShouldCloseDocument:NO when the
app is going to terminate. That's pretty ugly though. You might also
register the MyDocument for app termination notifications and modify the
behavior there, that should also fix the problem. That's a little less
ugly, I think.
Anyone with better solutions? Is there a true proper fix? Is it a bug?
Chris -
On Wednesday, April 18, 2001, at 02:15 AM, Bill Keirstead wrote:
> Problem with NSDocument, Multiple Views, and setShouldCloseDocument
I think this is a classic problem of over-retaining, and then having to
deal with the mess... So, try to track down the source of the extra
retain that creates the problem, and then remove the corresponding
releases.
> Here are the relevant snippets:
> ====================================
> ====================================
>
> @implementation MyDocument
> - (void) makeWindowControllers {
> m_pController = [ [ MainWindowController allocWithZone: [ self
> zone ] ] init ];
> [ self addWindowController: m_pController ];
> }
The controller should be retained for you when you do
addWindowController:, so try this:
- (void) makeWindowControllers {
MainWindowController* m_pController = [ [ MainWindowController
allocWithZone: [ self zone ] ] init ];
[ self addWindowController: m_pController ];
[m_pController release];
}
> ***snipped standard stuff***
>
> - (void)dealloc {
> printf( "MyDocument::dealloc\n" );
>
> [ m_pController autorelease ];
> [ super dealloc ];
> }
- (void)dealloc {
printf( "MyDocument::dealloc\n" );
[ super dealloc ];
}
> ====================================
> ====================================
>
> @implementation MainWindowController
> - (id)init {
> self = [ super initWithWindowNibName: @"MyDocument" ];
> return self;
> }
>
> - (void) windowDidLoad {
> [ self setShouldCloseDocument: YES ];
> }
>
> - (IBAction)open2ndWindow:(id)sender
> {
> AltWindowController *pWindowController =
> [ [ AltWindowController allocWithZone: [ [ self document ]
> zone ] ] init ];
> [ [ self document ] addWindowController: pWindowController ];
> }
>
> - (void)dealloc {
> printf( "MainWindowController::dealloc\n" );
> [ super dealloc ];
> }
> @end
> ====================================
> ====================================
>
> @implementation AltWindowController
> - (id)init {
> self = [ super initWithWindowNibName: @"AltWindowController" ];
> [ [ super window ] makeKeyAndOrderFront: self ];
> return self;
> }
>
> - (void)dealloc {
> printf( "AltWindowController::dealloc\n" );
> [ super dealloc ];
> }
>
> - (void) windowWillClose:(id) sender
> {
> printf( "AltWindowController::windowWillClose\n" );
> [ self autorelease ];
> }
Get rid of this auto release --- IIRC, it will be handled correctly by
default; it is your extra retain that creates the problem.
- (void) windowWillClose:(id) sender
{
printf( "AltWindowController::windowWillClose\n" );
[super windowWillClose:sender];
}
Regards,
John Hornkvist -
On Wednesday, April 18, 2001, at 03:31 AM, Bill Keirstead wrote:
>> printf( "AltWindowController::windowWillClose\n" );
If you do a lot of these, a macro like the following will save some
typing:
#define CMDlog NSLog(@"%@ :: %@",NSStringFromClass([self
class]),NSStringFromSelector(_cmd))
Regards,
John Hornkvist -
I responded to the original message in this thread over 12 hours ago,
but I haven't received it myself yet...
In short:
On Wednesday, April 18, 2001, at 08:20 AM, Chris Behm wrote:
> Anyone with better solutions? Is there a true proper fix? Is it a bug?
The problem is likely to be that the controller is retained here:
- (void) makeWindowControllers {
m_pController = [ [ MainWindowController allocWithZone: [ self
zone ] ] init ];
[ self addWindowController: m_pController ];
}
m_pController probably has a retain count of 2 here. A [m_pController
release] after adding it to the list of window controllers would
probably get rid of the problem, since you'd no longer have to worry
about releasing it yourself; that would be up to Apple's code...
Regards,
John Hornkvist -
On Wednesday, April 18, 2001, at 05:43 PM, John H=F6rnkvist wrote:
> I responded to the original message in this thread over 12 hours ago,=20=
> but I haven't received it myself yet...
I've been having weird problems like that too... no clue why
> In short:bug?
>
> On Wednesday, April 18, 2001, at 08:20 AM, Chris Behm wrote:
>> Anyone with better solutions? Is there a true proper fix? Is it a =
>
> The problem is likely to be that the controller is retained here:
> - (void) makeWindowControllers {
> m_pController =3D [ [ MainWindowController allocWithZone: [ self=20=
> zone ] ] init ];
> [ self addWindowController: m_pController ];
> }
>
> m_pController probably has a retain count of 2 here. A [m_pController=20=
> release] after adding it to the list of window controllers would=20
> probably get rid of the problem, since you'd no longer have to worry=20=
> about releasing it yourself; that would be up to Apple's code...
The main window isn't being released multiple times, only once. The=20
problem is the sub-windows. They are being released once by the document=20=
when it closes (when the main window closes) and then once by=20
NSApplication when it makes all the open windows perform their close.=20
With any luck, the original poster has figured it out by now.
Chris -
On Thursday, April 19, 2001, at 12:19 AM, Chris Behm wrote:
> On Wednesday, April 18, 2001, at 05:43 PM, John H=F6rnkvist wrote:bug?
>> In short:
>>
>> On Wednesday, April 18, 2001, at 08:20 AM, Chris Behm wrote:
>>> Anyone with better solutions? Is there a true proper fix? Is it a =
>>
>> The problem is likely to be that the controller is retained here:
>> - (void) makeWindowControllers {
>> m_pController =3D [ [ MainWindowController allocWithZone: [ self=20=
>> zone ] ] init ];
>> [ self addWindowController: m_pController ];
>> }
>>
>> m_pController probably has a retain count of 2 here. A [m_pController=20=
>> release] after adding it to the list of window controllers would=20
>> probably get rid of the problem, since you'd no longer have to worry=20=
>> about releasing it yourself; that would be up to Apple's code...
>
> The main window isn't being released multiple times, only once. The=20
> problem is the sub-windows. They are being released once by the=20
> document when it closes (when the main window closes) and then once by=20=
> NSApplication when it makes all the open windows perform their close.=20=
> With any luck, the original poster has figured it out by now.
I did the changes I proposed, and it worked very well. Close the=20
secondary window; the AltWindowController gets release. Don't close it,=20=
and close the document: both get deallocated. No crash. Close the app:=20=
No crash. No leaks, as far as I can see.
Here is the code I used:
@interface MainWindowController:NSWindowController
- (IBAction)open2ndWindow:(id)sender;
@end
@interface AltWindowController:NSWindowController
@end
@interface MyDocument : NSDocument
{
}
@end
@implementation MyDocument
- (void) makeWindowControllers {
MainWindowController* m_pController =3D [ [ MainWindowController=20
allocWithZone: [ self zone ] ] init ];
[ self addWindowController: m_pController ];
[m_pController release]; // Gets rid of the extra retain
}
- (void)dealloc {
printf( "MyDocument::dealloc\n" );
[ super dealloc ];
}
// Standard stuff snipped
@end
@implementation MainWindowController
- (id)init {
self =3D [ super initWithWindowNibName: @"MyDocument" ];
return self;
}
- (void) windowDidLoad {
[ self setShouldCloseDocument: YES ];
}
- (IBAction)open2ndWindow:(id)sender
{
AltWindowController *pWindowController =3D
[ [ AltWindowController allocWithZone: [ [ self document ]=20
zone ] ] init ];
printf( "MainWindowController::open2ndWindow:\n" );
=09
[ [ self document ] addWindowController: pWindowController ];
[pWindowController release]; // Gets rid of the extra retain
}
- (void)dealloc {
printf( "MainWindowController::dealloc\n" );
[ super dealloc ];
}
@end
@implementation AltWindowController
- (id)init {
printf( "AltWindowController::init:\n" );
self =3D [ super initWithWindowNibName: @"AltWindowController" ];
[ [ self window ] makeKeyAndOrderFront: self ];
return self;
}
- (void)dealloc {
printf( "AltWindowController::dealloc\n" );
[ super dealloc ];
}
- (void) windowWillClose:(id) sender
{
printf( "AltWindowController::windowWillClose\n" );
}
@end
Regards,
John Hornkvist -
On Wednesday, April 18, 2001, at 06:47 PM, John H=F6rnkvist wrote:
> On Thursday, April 19, 2001, at 12:19 AM, Chris Behm wrote:
>
>> On Wednesday, April 18, 2001, at 05:43 PM, John H=F6rnkvist wrote:
>>> In short:
>>>
>>> On Wednesday, April 18, 2001, at 08:20 AM, Chris Behm wrote:
>>>> Anyone with better solutions? Is there a true proper fix? Is it a=20=
>>>> bug?
>>>
>>> The problem is likely to be that the controller is retained here:
>>> - (void) makeWindowControllers {
>>> m_pController =3D [ [ MainWindowController allocWithZone: [ self=20=
>>> zone ] ] init ];[m_pController=20
>>> [ self addWindowController: m_pController ];
>>> }
>>>
>>> m_pController probably has a retain count of 2 here. A =
>>> release] after adding it to the list of window controllers would=20
>>> probably get rid of the problem, since you'd no longer have to worry=20=
>>> about releasing it yourself; that would be up to Apple's code...
>>
>> The main window isn't being released multiple times, only once. The=20=
>> problem is the sub-windows. They are being released once by the=20by=20
>> document when it closes (when the main window closes) and then once =
>> NSApplication when it makes all the open windows perform their close.=20=
>> With any luck, the original poster has figured it out by now.it,=20
>
> I did the changes I proposed, and it worked very well. Close the=20
> secondary window; the AltWindowController gets release. Don't close =
> and close the document: both get deallocated. No crash. Close the app:=20=
> No crash. No leaks, as far as I can see.The thing is, NSWindowController's docs state that if one wants the=20
>
> Here is the code I used:
> [snip]
> - (void) windowWillClose:(id) sender
> {
> printf( "AltWindowController::windowWillClose\n" );
> }
> @end
>
controller to go away when the window closes, in the windowWillClose=20
method, one adds [self autorelease]. If you add that to your=20
windowWillClose method, then you'll get the crashes (or should).
I reproduced the same problem with two objects and main. Main allocates=20=
object1 and object2 and adds object2 to object1's children. Doing so=20
sets object2's parent to object1 (addWindowController). Main also stores=20=
object1 and object2 in an array ([NSApp windows]). Main then releases=20
object1 and object2 (this gives them proper retain counts and is the=20
same step as [m_pController release] and [pWindowController release]).
When main exits, it calles [my_objects=20
makeObjectsPerformSelector:@selector(sim_close)]. This calls sim_close=20=
on object1 and object2 (object1 first). When object1 closes, it use=20
makeObjectsPerformSelector to tell all it's children to sim_close=20
(NSDocument does this to close sub-windows).
Since object2 is a child of object1, object2 gets a sim_close from=20
object1. It tells object1 to remove object2 from object1's children and=20=
then [self autorelease] (as per the NSWindowController docs).
Now the bad ref to object2 from main's my_objects is told to sim_close=20=
and the app SIGBUSes.
The issue comes down to this, is the window controller supposed to=20
autorelease? In a non-document app or a doc-app without sub-windows,=20
using [self autorelease] is fine. In fact, it is often the desired=20
procedure so that memory is properly released. It only presents a=20
problem with doc-apps and sub-windows.
Not trying to be a nuisance or anything, just saying that, according to=20=
the docs, the code is right and what happens is wrong. Otherwise one=20
needs to step in before NSApplication begins closing windows and do=20
something like setShouldClose to NO or it'll SIGBUS (or similar).
Phew :),
Chris
P.S. I've got the code I wrote to illustrate this if anyone wants it=20
(not sure why anyone would, but figured I'd offer). -
Thanks to both Chris and John I now have a much better understanding of
what is going on. John's solution does seem to work flawlessly and
makes sense too.
As to whether or not this behavior is consistent with the documentation,
I can't say for sure. The sentence that I keep seeing repeated in many
documents seems to indicate that you should use windowWillClose and
[self autorelease] for controllers that are not part of a document, but
doesn't explicitly say what to do for windows that do participate in a
document. It may be there and I just don't see it.
From the documentation:
------------------------
"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 NSWindow's NSWindowWillCloseNotification
or, as window delegate, implement the windowWillClose: method and in
your implementations include the following line of code:
[self autorelease];
------------------------
Thanks again to all. Sorry for not replying right away. I have been
out all day.
- Bill Keirstead
On Wednesday, April 18, 2001, at 06:47 PM, John Hörnkvist wrote:
>> On Wednesday, April 18, 2001, at 05:43 PM, John Hörnkvist wrote:
>>> In short:
>>>
>>> On Wednesday, April 18, 2001, at 08:20 AM, Chris Behm wrote:
>>>> Anyone with better solutions? Is there a true proper fix? Is it a
>>>> bug?
>>>
>>> The problem is likely to be that the controller is retained here:
>>> - (void) makeWindowControllers {
>>> m_pController = [ [ MainWindowController allocWithZone: [ self
>>> zone ] ] init ];
>>> [ self addWindowController: m_pController ];
>>> }
>>>
>>> m_pController probably has a retain count of 2 here. A [m_pController
>>> release] after adding it to the list of window controllers would
>>> probably get rid of the problem, since you'd no longer have to worry
>>> about releasing it yourself; that would be up to Apple's code...
>>
>> The main window isn't being released multiple times, only once. The
>> problem is the sub-windows. They are being released once by the
>> document when it closes (when the main window closes) and then once by
>> NSApplication when it makes all the open windows perform their close.
>> With any luck, the original poster has figured it out by now.
>
> I did the changes I proposed, and it worked very well. Close the
> secondary window; the AltWindowController gets release. Don't close it,
> and close the document: both get deallocated. No crash. Close the app:
> No crash. No leaks, as far as I can see.
-
On Thursday, April 19, 2001, at 02:45 AM, Chris Behm wrote:
> method, one adds [self autorelease]. If you add that to your
> windowWillClose method, then you'll get the crashes (or should).
Of course it would crash! There are no extra references; I took care of
those earlier.
Quoting the docs:
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 NSWindow's NSWindowWillCloseNotification
or, as window delegate, implement the windowWillClose: methodand in your
implementations include the following line of code:
[self autorelease];"
You only do that if your window controller isn't part of a document, but
in the example, the window controller is part of the document, since it
was added to the documents set of window controllers.
I'd be wary of any code that needs to do [self autorelease]; it is very
likely to create problems like this. When one can't let the document
worry about releasing window controllers, it is probably better to
handle them from some other object that will be around for the lifetime
of the window, such as an application delegate.
Regards,
John Hornkvist -
On Wednesday, April 18, 2001, at 03:31 AM, Bill Keirstead wrote:
> printf( "AltWindowController::windowWillClose\n" );
On Wednesday, April 18, 2001, at 06:53 AM, John Hörnkvist wrote:
> If you do a lot of these, a macro like the following will save some
> typing:
> #define CMDlog NSLog(@"%@ :: %@",NSStringFromClass([self
> class]),NSStringFromSelector(_cmd))
As a fairly random side note, you can also use:
NSLog(@"%s", __PRETTY_FUNCTION__);
Within a method, this will result in output such as:
-[Shtuff doSomething:]
---
Luke



