Implementing binding with garbage collection.

  • Thanks, everyone, for your private replies, but I'm getting some
    contradictory information so I'm going to try to duke it out in the
    public forum again ;-)

    The basic premise is that I have an NSView subclass in a GC-Required
    app which has an NSArray* property called contentArray. I would like
    to bind contentArray to the arrangedObjects property of an
    NSArrayController. In this case, my NSView subclass conveniently
    contains an outlet to said controller. My code therefore looks
    something like this:

    @interface MyView : NSView {
        IBOutlet NSArrayController *myArrayController;
    }
    @property(copy)NSArray *contentArray;
    @property(copy) NSString *observedKeyPath;
    @property id observedObject;
    @end

    @implementation MyView
    @synthesize contentArray, observedObject, observedKeyPath;

    -(void)awakeFromNib{
        [self bind:@"contentArray" toObject:arrayController
    withKeyPath:@"arrangedObjects" options:nil]
    }

    -(void)bind:(NSString *)binding toObject:(id)observableController
    withKeyPath:(NSString *)keyPath options:(NSDictionary *)options{
        if([binding isEqualToString:@"entities"]){
          self.observedObject = observableController;
          self.observedKeyPath = keyPath;
          [observableController addObserver:self forKeyPath:keyPath
    options:0 context:someContext];
        }else{
          [super bind:binding toObject:observableController
    withKeyPath:keyPath options:options];
        }
    }

    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
    change:(NSDictionary *)change context:(void *)context{
      //update contentArray when the obeservedObject changes...
    }

    -(void)setContentArray:(NSArray*)anArray{
      //communicate changes back to observedObject...
    }

    -(void)finally{
        [self.observedObject removeObserver:self
    forKeyPath:self.observedKeyPath];
        self.observedObject = nil;
        self.observedKey = nil;
    }
    @end

    My concern is with the -finally. I basically just moved my -dealloc
    code over there, but in doing so I'm explicitly performing two of the
    things the documentation says never to perform in -finally:

    "To make your finalize method as efficient as possible, you should
    typically not do any of the following:
    o Set instance variables to nil
    o Remove self as an observer of a notification center (in a garbage
    collected environment, notification centers use zeroing weak
    references)."

    Now to be fair, it's not a notification center that I'm observing, but
    it's been suggested the same applies? And while I could get away with
    not nilling out the keypath, I'm unclear what will happen if I don't
    clear my reference to the array controller. On top of all of that,
    it's not clear to me in my testing that -finally is reliably called to
    begin with.

    All of this combines to make coding a binding implementation even more
    convoluted than it was in 10.4. I feel certain that can't be the case
    and that I am again overlooking something. Does anyone have experience
    to share?

    Many thanks,
    -Joshua Emmons
  • On Nov 8, 2007, at 12:52 PM, Joshua Emmons wrote:
    > The basic premise is that I have an NSView subclass in a GC-Required
    > app which has an NSArray* property called contentArray. I would like
    > to bind contentArray to the arrangedObjects property of an
    > NSArrayController. In this case, my NSView subclass conveniently
    > contains an outlet to said controller. My code therefore looks
    > something like this:
    > [...]
    > -(void)finally{
    > [self.observedObject removeObserver:self
    > forKeyPath:self.observedKeyPath];
    > self.observedObject = nil;
    > self.observedKey = nil;
    > }
    > @end
    >
    > My concern is with the -finally. I basically just moved my -dealloc
    > code over there, but in doing so I'm explicitly performing two of
    > the things the documentation says never to perform in -finally:
    > "To make your finalize method as efficient as possible, you should
    > typically not do any of the following:
    > o Set instance variables to nil
    > o Remove self as an observer of a notification center (in a garbage
    > collected environment, notification centers use zeroing weak
    > references)."
    > Now to be fair, it's not a notification center that I'm observing,
    > but it's been suggested the same applies?
    >
    No, the same does not apply (unfortunately).

    > All of this combines to make coding a binding implementation even
    > more convoluted than it was in 10.4. I feel certain that can't be
    > the case and that I am again overlooking something. Does anyone have
    > experience to share?
    >
    <http://homepage.mac.com/mmalc/CocoaExamples/controllers.html#unbinding>
    You can use viewWillMoveToSuperview:.
    Note that this only works if your view is not going to become full-
    screen, otherwise you will have to write a finalize method.

    mmalc
  • I'm sorry, I didn't mean to make my previous reply off-list.  Here it is again:

    Also, you aren't seeing -finally invoked because the correct method
    name is -finalize.

    > I seem to recall that, with GC enabled, I no longer have to worry
    > about unregistering observers as they get zeroed out automagically
    > when collected?

    This applies to unregistering observers with NSNotificationCenter,
    but unfortunately not to unregistering KVO observers.  You still need
    to remove these observers manually.  You need to manually unbind as
    well.

    It's possible that this will be improved in the future.  The extra
    -removeObserver: calls won't hurt anything but code cleanliness if
    they become unnecessary.

    -dealloc is never invoked under gc.  -finalize is.  It's better if you
    can avoid implementing finalize (turns out the things are really hard
    to get right!) but unregistering a KVO observer is one of the reasons
    you might need to.

    In practice, it's often possible to guarantee for other reasons that
    an object has been unregistered before it could be collected.
    For example, you may rip down observers when a window is closed.
    This is good practice.  Especially in gc (but even without) doing
    stuff besides releasing objects in memory in dealloc/finalize is sketchy.
    Since anything in the entire framework can retain your objects, you
    never really know when dealloc is going to be called, if ever.

    Ken Ferry
    Cocoa Frameworks
  • On Nov 8, 2007, at 3:10 PM, mmalc crawford wrote:

    > >
    > You can use viewWillMoveToSuperview:.
    > Note that this only works if your view is not going to become full-
    > screen, otherwise you will have to write a finalize method.

    Could the following be a workaround to this?

    Create a 'container view' and add the view with bindings as a subview
    to it.  The container view's drawRect can just be a NOP.  Then, call
    enterFullScreenMode:withOptions: on the container view.

    I have not played with those new full-screen APIs, so not sure if this
    would work or not.  However, since the view with bindings would then
    have a non-nil superview, bindings would not go away when going full-
    screen.

    ___________________________________________________________
    Ricky A. Sharp        mailto:<rsharp...>
    Instant Interactive(tm)  http://www.instantinteractive.com
  • On Nov 8, 2007, at 1:34 PM, Ricky Sharp wrote:

    > Create a 'container view' and add the view with bindings as a
    > subview to it.  The container view's drawRect can just be a NOP.
    > Then, call enterFullScreenMode:withOptions: on the container view.
    >
    > I have not played with those new full-screen APIs, so not sure if
    > this would work or not.  However, since the view with bindings would
    > then have a non-nil superview, bindings would not go away when going
    > full-screen.

    I haven't tried this either, but I can't think of a reason off-hand
    why it wouldn't work.

    I often find myself using just plain NSView objects as containers for
    organizational purposes.  This has only increased with the
    introduction of NSViewController in Leopard.  It's my new favorite
    class! Mmm, modularity.

    These days I typically create an NSViewController subclass for
    whatever higher-level "component" I'm designing, and have its view be
    a plain NSView that acts as a container for everything else within the
    module (buttons, text fields, table views, etc.).

      -- Chris
previous month november 2007 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