Retain cycle problem with bindings & NSWindowController
-
OK, I've been beating my head against this problem all day, and I must
be doing something dumb because I haven't been able to find anything
with a search on the web and on this mailing list to indicate that
anyone else has a problem with this.
My question is: is it possible to use bindings when the nib "File's
Owner" is an NSWindowController subclass.
Using ObjectAlloc to diagnose memory leaks, it appears that my window
controller doesn't go away when the window is closed. It looks like the
extra retains are coming from the bindings. If I eliminate all
bindings, the window controller will go away when the window is closed.
It looks like the window will not be dealloced until the window
controller is dealloced, but the window controller is not dealloced
because the bindings are retaining it, so I have a retain cycle.
I don't see any way to specify the path to the model property for a
binding except through the File's Owner, so it looks like the File's
Owner can't be the window controller. But that can't be right. However,
the available examples I've found so far use an NSDocument subclass as
the File's Owner. Of course, this is understandable for tutorial
examples. I'm still looking though, so if anyone can point me to a
working example where an NSWindowController subclass is used for the
File's Owner in a nib that uses bindings, I'd appreciate ti.
- Dennis D.
_______________________________________________
cocoa-dev mailing list | <cocoa-dev...>
Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored. -
On 7. Jun 2004, at 9:33, Dennis C. De Mars wrote:
> My question is: is it possible to use bindings when the nib "File's
> Owner" is an NSWindowController subclass.
I have this problem as well (and did mention it a few times on this
list w/o responses ;) ).
The documentation for addObserver:forKeyPath:options:context: says that
"Neither the receiver or anObserver are retained", so it should not
happen solely by having the view observe the controller/model.
But the documentation for bind:toObject:withKeyPath:options: does not
make such a guarantee, and surely, it is only some bindings which cause
a retain cycle.
> I don't see any way to specify the path to the model property for a
> binding except through the File's Owner, so it looks like the File's
> Owner can't be the window controller. But that can't be right.
I hope you'll report it as a bug -- I haven't come around to doing that
myself yet, so many bugs, so little time, and so little incentive to do
so ;)
> However, the available examples I've found so far use an NSDocument
> subclass as the File's Owner [...]
Unfortunately that won't solve anything.
Document retains window controller,
window controller retains top level nib objects,
top level nib objects retain children,
children retain document (because of bindings),
-> and there's your cycle again!
_______________________________________________
cocoa-dev mailing list | <cocoa-dev...>
Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored. -
On Jun 7, 2004, at 10:29 AM, Allan Odgaard wrote:
> On 7. Jun 2004, at 9:33, Dennis C. De Mars wrote:
>
>> My question is: is it possible to use bindings when the nib "File's
>> Owner" is an NSWindowController subclass.
>
> I have this problem as well (and did mention it a few times on this
> list w/o responses ;) ).
>
> The documentation for addObserver:forKeyPath:options:context: says
> that "Neither the receiver or anObserver are retained", so it should
> not happen solely by having the view observe the controller/model.
>
> But the documentation for bind:toObject:withKeyPath:options: does not
> make such a guarantee, and surely, it is only some bindings which
> cause a retain cycle.
>
>> I don't see any way to specify the path to the model property for a
>> binding except through the File's Owner, so it looks like the File's
>> Owner can't be the window controller. But that can't be right.
>
> I hope you'll report it as a bug -- I haven't come around to doing
> that myself yet, so many bugs, so little time, and so little incentive
> to do so ;)
OK, I think I'll do that. I just wanted to check that I wasn't
forgetting anything obvious.
I will have to investigate further as to which bindings cause this. I
have found you don't even need a binding per se to have this problem. I
tried instantiating an NSObjectController in my nib file to access the
model indirectly, under the theory that the binding would retain the
NSObjectController and not the File's Owner, but no such luck. Even if
you don't have any bindings, just connecting the NSObjectController's
content outlet to the File's Owner will retain it. Drat!
>
>> However, the available examples I've found so far use an NSDocument
>> subclass as the File's Owner [...]
>
> Unfortunately that won't solve anything.
>
> Document retains window controller,
> window controller retains top level nib objects,
> top level nib objects retain children,
> children retain document (because of bindings),
>
> -> and there's your cycle again!
Well, yes and no. You definitely have a retain cycle there, as I
realized after I made my last post. But, this only causes a memory leak
if each object only releases the object it retains in its dealloc
method. But if one of the objects can release its retained object under
other circumstances, it can break the cycle.
For instance, the document object can know when the window controller's
widow is closed. It can then release the window controller and that can
propagate down the chain until the document itself gets released (at
which point the document has to make sure that it doesn't send a
release to the window controller that it already released).
I am not privy to the internal workings of NSDocument but it seems
quite possible something like this is going on, so I tried a little
experiment. I made a fresh project with a document and put one text
field in the window with a binding to a variable I put in MyDoucment.
After building, I ran the application under ObjectAlloc. After closing
the window, the MyDocument object was deallocated.
Then I created a second project and defined an NSWindowController
subclass. I created an identical text field, but the binding had a
model path like: document.testField.
In this case, the NSWindowController object did not get deallocated
after the window was closed, as observed by ObjectAlloc.
So, even in this simplest of examples the NSWindowController object is
caught in a deadly embrace with the view object binding to it, which
the NSDocument seems to be able to break.
- Dennis D.
_______________________________________________
cocoa-dev mailing list | <cocoa-dev...>
Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored. -
On 8. Jun 2004, at 11:06, Dennis C. De Mars wrote:
> So, even in this simplest of examples the NSWindowController object is
> caught in a deadly embrace with the view object binding to it, which
> the NSDocument seems to be able to break.
The problem I had did involve NSDocument, but I do think that a window
controller is more susceptible to the retain-cycle problem.
My solution was to send unbind: messages in the
windowWillClose:-notification.
_______________________________________________
cocoa-dev mailing list | <cocoa-dev...>
Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored. -
On Jun 8, 2004, at 2:46 AM, Allan Odgaard wrote:
> On 8. Jun 2004, at 11:06, Dennis C. De Mars wrote:
>
>> So, even in this simplest of examples the NSWindowController object
>> is caught in a deadly embrace with the view object binding to it,
>> which the NSDocument seems to be able to break.
>
> The problem I had did involve NSDocument, but I do think that a window
> controller is more susceptible to the retain-cycle problem.
>
> My solution was to send unbind: messages in the
> windowWillClose:-notification.
I believe that the NSWindowController won't be released until
NSApplication selects a different main window (or key window?), if the
closed window in question was the only visible window at the time.
Moving the cursor over a visible (non-key) utility window also triggers
the release. Closing the window does release/remove its subviews,
caches & backing store, but the window object itself is still being
retained by NSApp somewhere. I'm in total agreement about IB and
excessive retaining of bound objects, though...
--
Shaun Wexler
MacFOH
http://www.macfoh.com
_______________________________________________
cocoa-dev mailing list | <cocoa-dev...>
Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored. -
On Jun 8, 2004, at 2:46 AM, Allan Odgaard wrote:
> On 8. Jun 2004, at 11:06, Dennis C. De Mars wrote:
>
>> So, even in this simplest of examples the NSWindowController object
>> is caught in a deadly embrace with the view object binding to it,
>> which the NSDocument seems to be able to break.
>
> The problem I had did involve NSDocument, but I do think that a window
> controller is more susceptible to the retain-cycle problem.
>
> My solution was to send unbind: messages in the
> windowWillClose:-notification.
OK, I can see that this would work. Do you have to send an unbind
message to each view object that has a binding?
Based on my experiments with bindings in very simple examples with an
NSDocument class and an NSWindowController class as File's Owner
respectively, I came up with a paradigm for using bindings while
avoiding the retain cycle.
My first observation was that the document as File's Owner was working
properly with bindings to the document model objects. My assumption is
that this works because the bindings retain the document, the document
retains the window controller and the window controller retains the
window (which retains the view objects with the bindings) and that the
NSDocument has the ability to release the window controller and window
when it knows it is closing. This happens when it receives the close
message, it doesn't have to happen in the dealloc method, which is why
it can break the retain cycle.
Actually, in the case where the window controller is File's Owner, the
window controller could also do this in theory, but it apparently
doesn't -- it maintains a retain on the window even after it knows the
window is supposed to close, relying on its dealloc to do the final
release, which never happens because the bindings have retains on the
window controller. If the window controller could be convinced to
completely release the window before it is itself dealloced then the
cycle could be broken but there is no way I know of to do this --
clearly NSWindowController was written with the assumption that the
window and subviews of the window would not retain the controller,
which is no longer the case when bindings are used. If
NSWindowController could be redesigned to completely release all of its
retains on the window (even if only on user request) all of the
following would be unnecessary.
So my first thought was that I would create some "model object" or
proxy for the model objects that would be the File's Owner. The window
controller would then be created programatically. It seemed to me the
window controller would not be of much use if it could not be File's
Owner, but then I realize that it could still be set manually to be the
window's delegate, so it would still have some functionality. Not being
able to connect window controller outlets in the nib file would still
be a significant disadvantage.
Then I had two more thoughts that make the scheme more workable:
1) The model object proxy would be not much more than a conduit to my
document object if I keep all the model objects there, so why not just
make the document object the File's Owner? The difference from the
conventional arrangement would be that the document would not be
allowed to create its own internal window controller (so we can still
have our own custom window controller).
2) The window controller object, then, can be instantiated as a top
level object in the same nib file. No binding will be made to window
controller object, but since it is in the nib file it can connect
outlets to view object and be the target of actions from view objects.
So this is what I did. I set the nib file up as above. I instantiate
the nib file in the document class' -makeWindowControllers method. In
-makeWindowControllers, the nib file is instantiated using NSNib.
The document class needs two outlets for the window controller and the
window, which are both top level objects. The document object is
responsible for releasing these objects, since the nib file was
instantiated via NSNib. Using the window controller outlet, the
documemt adds the window controller using -addWindowController. The
window controller can then be released since the document is retaining
it in its window controller list.
You have to make sure the top level objects (the window, and, if you
didn't already release it, the window controller) are released in the
close method of the document. This is enough to break the retain cycle.
That's about it, except for making all the right connections in IB. I
tried this on a simple example and it seems to work. I haven't
exercised it with a complex example so I don't know if there are any
gotchas due to the fact that it deviates somewhat from the standard
paradigms for using nib files with documents and window controllers.
Anyway, the best thing would be for Apple to fix the binding retain
situation so I would have to go through all this!
- Dennis D.
_______________________________________________
cocoa-dev mailing list | <cocoa-dev...>
Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored. -
On 9. Jun 2004, at 4:49, Dennis C. De Mars wrote:
>> My solution was to send unbind: messages in the
>> windowWillClose:-notification.
> OK, I can see that this would work. Do you have to send an unbind
> message to each view object that has a binding?
At least all those which cause retainment of the involved parties. But
in my case I only had a single binding, so not much of a problem...
> [...] If the window controller could be convinced to completely
> release the window before it is itself dealloced then the cycle could
> be broken but there is no way I know of to do this
You can send setWindow: with nil to the window controller.
This could be in a response to a 'window will close'-notification. But
there are cases where the window may re-open, and thus one needs to
have the class responsible for releasing the window controller also
perform this workaround.
> Anyway, the best thing would be for Apple to fix the binding retain
> situation so I would have to go through all this!
Indeed, there is probably a lot of applications out there which doesn't
work around this problem and thus leaks...
_______________________________________________
cocoa-dev mailing list | <cocoa-dev...>
Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored. -
On Jun 8, 2004, at 8:33 PM, Allan Odgaard wrote:
> On 9. Jun 2004, at 4:49, Dennis C. De Mars wrote:
>
>>> My solution was to send unbind: messages in the
>>> windowWillClose:-notification.
>> OK, I can see that this would work. Do you have to send an unbind
>> message to each view object that has a binding?
>
> At least all those which cause retainment of the involved parties. But
> in my case I only had a single binding, so not much of a problem...
>
>> [...] If the window controller could be convinced to completely
>> release the window before it is itself dealloced then the cycle could
>> be broken but there is no way I know of to do this
>
> You can send setWindow: with nil to the window controller.
Thanks! I missed that possibility. I'm going to try it out, it might
save me from having to go through all that folderol I outlined in my
previous message.
It looks to me like I could override the NSWindowController -close
method and use it there. Well, I'll experiment and see what I can do.
>
> This could be in a response to a 'window will close'-notification.
> But there are cases where the window may re-open, and thus one needs
> to have the class responsible for releasing the window controller also
> perform this workaround.
>
>> Anyway, the best thing would be for Apple to fix the binding retain
>> situation so I would have to go through all this!
>
> Indeed, there is probably a lot of applications out there which
> doesn't work around this problem and thus leaks...
Yes! I was thinking the same thing through this entire discussion. This
is probably hitting the vast majority of people using bindings but most
of them just don't realize it. With virtual memory, and disk space and
RAM being what they are nowadays, memory leaks of this kind go
unnoticed -- you have to look to find them. I just combed out most of
the memory leaks from one of my older applications and found more than
I expected. So I figured I would take a look at this new application I
am writing and try to get the memory management right while it is still
in an embryonic state. I was surprised to find, even in its current
primitive state, that there were memory leaks due to bindings. This
will eventually be a pretty complex application, so I want to try to do
things right from the start -- but if I had never looked at it with
ObjectAlloc, I would have been totally oblivious to the memory
management errors.
- Dennis D.
_______________________________________________
cocoa-dev mailing list | <cocoa-dev...>
Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored. -
On Jun 8, 2004, at 11:36 PM, Dennis C. De Mars wrote:
> On Jun 8, 2004, at 8:33 PM, Allan Odgaard wrote:
>
>> On 9. Jun 2004, at 4:49, Dennis C. De Mars wrote:
>>
>>>> My solution was to send unbind: messages in the
>>>> windowWillClose:-notification.
>>> OK, I can see that this would work. Do you have to send an unbind
>>> message to each view object that has a binding?
>>
>> At least all those which cause retainment of the involved parties.
>> But in my case I only had a single binding, so not much of a
>> problem...
>>
>>> [...] If the window controller could be convinced to completely
>>> release the window before it is itself dealloced then the cycle
>>> could be broken but there is no way I know of to do this
>>
>> You can send setWindow: with nil to the window controller.
>
> Thanks! I missed that possibility. I'm going to try it out, it might
> save me from having to go through all that folderol I outlined in my
> previous message.
>
> It looks to me like I could override the NSWindowController -close
> method and use it there. Well, I'll experiment and see what I can do.
>
>>
>> This could be in a response to a 'window will close'-notification.
>> But there are cases where the window may re-open, and thus one needs
>> to have the class responsible for releasing the window controller
>> also perform this workaround.
OK, this is a update to this thread to let everybody know how this
turned out (I wouldn't want anybody looking for info on this problem to
see this and think everything is resolved, because it isn't).
First of all, I'll point out that Allen Odgaard was right about the
"windowWillClose" notification or delegate method being the proper
place to put this "setWindow: with nil". I tried putting it in the
window controller -close method, but there is no guarantee it will be
called.
Also, it does do what is intended: it releases the window controller's
"retain" on the window.
I was so sure this would solve my binding/memory leak problems that I
installed it directly into my real application rather than trying it
first in one of my tiny test applications. The window controller still
hung around after I closed the window, though, so I went back to my
test code to see what was going on.
This test application is minimally modified from the cocoa
document-based project that is generated when you create a new project
in Xcode. This application has one custom window controller class, the
window controller is the File's Owner of a nib file with one window
that contains a single NSTextField control, and the text view has a
binding to a variable that is accessed with a path through the window
controller. When the window is closed, the window controller and window
are not released, even though when I have a similar setup with the
document as the File's Owner, everything _is_ released.
So, I rebuilt the application after adding the following method to the
window controller subclass:
- (void)windowWillClose:(NSNotification *)aNotification
{
[self setWindow:nil];
}
I ran it under ObjectAlloc and determined that the window controller
still doesn't go away when the window is closed. I decided to take a
look at the retain/release call sequence of the NSWindow to determine
why it did not get released, but I couldn'f find it. It _had been
released. The [self setWindow:nil]; statement did what was intended.
Hmmm.
I had already determined from looking at the retain/release sequence
for the window controller that it was almost certainly the retain from
the binding that wasn't being balanced with a release. I went looking
for the NSTextField object that had the binding.
ObjectAlloc said the NSTextField had also been released. But the
NSTextFieldCell object was still there.
At this point the theme music from "Twilight Zone" started playing in
my head. OK, why was this guy hanging around?
Turns out it was retained by the binding also.
It looks like the only way the retain cycle can be broken is, as Allen
suggested in a previous message, to send an unbind message to each view
object that has a binding. The question presents itself, why do things
work (as I outlined in a previous message) when the window controller
object is not the File's Owner, but you get memory leaks when it is the
File's Owner?
My theory at this point is that the window controller is the object
that is responsible for keeping track of the bindings and issuing
unbind commands to the view objects, but it can't do this when it is
itself a target of bindings (perhaps the unbind messages are issued in
the dealloc method).
Or maybe the truth is more complicated than that. It probably is, but
as it is all a black box to me, I think I've gone as far as I can in
trying to work around this problem. I have to get on with development.
I will definitely file a bug with Apple, though -- it's pretty clear
something needs to be fixed.
- Dennis D.
_______________________________________________
cocoa-dev mailing list | <cocoa-dev...>
Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored.


