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.
previous month june 2004 next month
MTWTFSS
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30        
Go to today