NSWindowController subclasses, and retain cycles

  • I have been battling a retain cycle for a couple of hours, assuming I am at fault, but I am starting to think there is something fishy going on. Can anybody advise?

    - I have a window controller subclass (and associated window) A, and a separate window controller subclass (and associated window) B. B contains a property, designated 'retain', that keeps a pointer to A. B is programatically closed first, and later on A is programatically closed
    - I believe that I am correctly balancing retain/release for A.
    - I believe I am doing the same for B.

    1. If I do not set B's pointer to A, both A and B are deallocated correctly
    2. If instead I set B's pointer to A, but reset it to nil just before B is closed, both A and B are deallocated correctly
    3. If instead B still has a live pointer to A when B is closed, this should be autoreleased by the runtime, which it is. However, A never gets deallocated.

    There appears to be some weird runtime voodoo behind the scenes involving NSAutounbinder, which I think is what should ultimately be sending the final release to A. That is what sends the final release to B, but that final release for A is missing under that third scenario above.

    I am concerned by the following post:
    http://theocacao.com/document.page/18
    This seems to imply there is some dodginess behind the scenes that Apple have bodged to try and make things work. The blog post is 8 years old, so I would have hoped this would be sorted by now(!), but it does sound remarkably close to my scenario here.

    I may have missed out some important relevant information here, so let me know if I need to be more specific about what I am doing, but can anybody suggest:
    - If you agree the problem is likely to be with the runtime rather than with my code
    - What the best workaround would be (scenario 2 above will probably do as a workaround...)
    - Whether the fact that I am hitting this suggests I am doing something out of the ordinary that I should probably be doing differently.

    Thanks for any advice
    Jonny
  • On 24 May 2012, at 9:26 AM, Jonathan Taylor wrote:

    > - I have a window controller subclass (and associated window) A, and a separate window controller subclass (and associated window) B. B contains a property, designated 'retain', that keeps a pointer to A. B is programatically closed first, and later on A is programatically closed
    > - I believe that I am correctly balancing retain/release for A.
    > - I believe I am doing the same for B.
    >
    > 1. If I do not set B's pointer to A, both A and B are deallocated correctly
    > 2. If instead I set B's pointer to A, but reset it to nil just before B is closed, both A and B are deallocated correctly
    > 3. If instead B still has a live pointer to A when B is closed, this should be autoreleased by the runtime, which it is. However, A never gets deallocated.

    Why do you believe that a retain @property does not always require a release (set to nil)? The autorelease you seem to have detected was sent by a completely different chain of execution to balance a retain made in that chain. The operating system does not magically know that you're done with B's retention of A; you have to balance your own retains with your own releases. (2) works because it is correct.

    — F
  • On May 24, 2012, at 10:21 AM, Fritz Anderson wrote:

    > On 24 May 2012, at 9:26 AM, Jonathan Taylor wrote:
    >
    >> - I have a window controller subclass (and associated window) A, and a separate window controller subclass (and associated window) B. B contains a property, designated 'retain', that keeps a pointer to A. B is programatically closed first, and later on A is programatically closed
    >> - I believe that I am correctly balancing retain/release for A.
    >> - I believe I am doing the same for B.
    >>
    >> 1. If I do not set B's pointer to A, both A and B are deallocated correctly
    >> 2. If instead I set B's pointer to A, but reset it to nil just before B is closed, both A and B are deallocated correctly
    >> 3. If instead B still has a live pointer to A when B is closed, this should be autoreleased by the runtime, which it is. However, A never gets deallocated.
    >
    > Why do you believe that a retain @property does not always require a release (set to nil)? The autorelease you seem to have detected was sent by a completely different chain of execution to balance a retain made in that chain. The operating system does not magically know that you're done with B's retention of A; you have to balance your own retains with your own releases. (2) works because it is correct.

    It’s ambiguous from the original post, but since he only points out that A never gets deallocated, my assumption is that B *does* get deallocated. If this is the case, A should get released in B’s dealloc method (unless he’s not using ARC and forgot to do this).

    Charles
  • On May 24, 2012, at 10:26 AM, Jonathan Taylor <j.m.taylor...> wrote:

    > I have been battling a retain cycle for a couple of hours, assuming I am at fault, but I am starting to think there is something fishy going on. Can anybody advise?
    >
    > - I have a window controller subclass (and associated window) A, and a separate window controller subclass (and associated window) B. B contains a property, designated 'retain', that keeps a pointer to A. B is programatically closed first, and later on A is programatically closed
    > - I believe that I am correctly balancing retain/release for A.
    > - I believe I am doing the same for B.

    Please post your code.

    >
    > 1. If I do not set B's pointer to A, both A and B are deallocated correctly
    > 2. If instead I set B's pointer to A, but reset it to nil just before B is closed, both A and B are deallocated correctly
    > 3. If instead B still has a live pointer to A when B is closed, this should be autoreleased by the runtime, which it is. However, A never gets deallocated.

    If A somehow has a chain of retains that keeps B alive, #3 is wrong. Your approach with #2 seems sensible.

    >
    > There appears to be some weird runtime voodoo behind the scenes involving NSAutounbinder, which I think is what should ultimately be sending the final release to A. That is what sends the final release to B, but that final release for A is missing under that third scenario above.

    It sounds like you're (erroneously) associating an autorelease coming from some framework code with a retain that you're performing yourself.

    >

    --Kyle Sluder
  • On May 24, 2012, at 9:26 AM, Jonathan Taylor wrote:

    > There appears to be some weird runtime voodoo behind the scenes involving NSAutounbinder, which I think is what should ultimately be sending the final release to A. That is what sends the final release to B, but that final release for A is missing under that third scenario above.
    >
    > I am concerned by the following post:
    > http://theocacao.com/document.page/18
    > This seems to imply there is some dodginess behind the scenes that Apple have bodged to try and make things work. The blog post is 8 years old, so I would have hoped this would be sorted by now(!), but it does sound remarkably close to my scenario here.

    NSAutounbinder is part of the solution to the problem noted at that blog.  NSWindowController has special logic to break the retain cycle inherent in binding through File's Owner, which is why you should always use one to own a window NIB (and use NSViewController to own a view NIB).

    The documentation for this is nearly lost to time, but there's still one oblique reference.  In the old AppKit release notes, where it discusses the then-new NSViewController class, it says:

    "• Does the same sort of memory management of top-level objects that NSWindowController does, taking the same care to prevent reference cycles when controls are bound to the nib file's owner that NSWindowController began taking in Mac OS 10.4."

    I suspect you have a memory management bug in your own code.

    Regards,
    Ken
  • On 24 May 2012, at 16:27, Kyle Sluder wrote:
    >> 1. If I do not set B's pointer to A, both A and B are deallocated correctly
    >> 2. If instead I set B's pointer to A, but reset it to nil just before B is closed, both A and B are deallocated correctly
    >> 3. If instead B still has a live pointer to A when B is closed, this should be autoreleased by the runtime, which it is. However, A never gets deallocated.
    >
    > If A somehow has a chain of retains that keeps B alive, #3 is wrong. Your approach with #2 seems sensible.

    Thankyou all three for your replies. [I am not using ARC as this must run on Snow Leopard, incidentally].

    Having done some playing around with trivial test cases, it appears that I have completely misunderstood a basic concept of properties - apologies for wasting your time. I had always thought that if I declare a property as follows:

    @property (retain) MyProgressWindow * progressObject;
    ...
    @synthesize progressObject = _progressObject;

    then if that property has a non-nil value at the point that the instance is deallocated, a release would be sent. That had always struck me as the "obvious" behaviour, but it appears that this does not happen. I am sure there is a good reason for that! Not sure how I have managed to go this long without spotting that I am wrong here. Seems a bit of a pain to have to go through setting everything to nil in the dealloc method (which I would otherwise not have to even implement at all in many cases), but looks like that's what I need to do...

    Cheers
    Jonny
previous month may 2012 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 31      
Go to today