Self Observation

As far as I can tell, you can’t count on the order of observer registration for KVO to relate to order of notification. This is a bummer because it can lead to really subtle bugs for certain KVO and bindings patterns. My conclusion is to avoid those patterns, but if you’re interested in the details (and especially if you can point out a solution / flaws in my tests), read on!

In this particular case, I have a base drawable class (DGDrawable) that exposes a collection of child drawables.  I have a drawable sub-class (called DGNode) that adds a collection of DGPort objects (another DGDrawable sub-class).  DGNodes have special additional functionality associated with their child ports, but all the base class functionality of DGDrawable with respect to their children still applies.  DGNodes also repeat this pattern with other types of DGDrawable children, but for this example, I’ll just focus on the ports collection.

So what I’d like to be able do is have DGNode instances observe their port collection and edit the membership of their children collection to enjoy all the default DGDrawable behavior vis a vis child DGDrawables.

So for example, in the DGNode initializer, it registers to observe itself:

[self addObserver:self
       forKeyPath:@"ports"
          options:(NSKeyValueObservingOptionNew | 
                   NSKeyValueObservingOptionOld)
          context:nil];

And in it’s observer method, it edits it’s children collection to reflect changes to the ports collection:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
  int changeKind = [[change objectForKey:
    NSKeyValueChangeKindKey] intValue];
  if([keyPath isEqualToString:@"ports"])
  {
    if(changeKind == NSKeyValueChangeSetting ||
      changeKind == NSKeyValueChangeRemoval ||
      changeKind == NSKeyValueChangeReplacement)
    {
      NSMutableArray* children = 
        [self mutableArrayValueForKey:@"children"];
      for(DGPort* port in 
        [change objectForKey:NSKeyValueChangeOldKey])
      {
        [children removeObject:port];
        [port setNode:nil];
      }
    }
    if(changeKind == NSKeyValueChangeSetting ||
      changeKind == NSKeyValueChangeInsertion ||
      changeKind == NSKeyValueChangeReplacement)
    {
      NSMutableArray* children = 
        [self mutableArrayValueForKey:@"children"];
      for(DGPort* port in 
        [change objectForKey:NSKeyValueChangeOldKey])
      {
        [port setNode:self];
        [children addObject:port];
      }
    }
  }
}

Now, let’s say that another object external to the DGNode wants to use or observe the ports collection.  The addition of a port into the children collection is not guaranteed to happen with respect to when the external observer is notified (even though the DGNode registered it’s observation “first”). So in effect, the outside observer is told about changes to the DGNode when it’s in an “intermediate” state (i.e. the ports have been updated, but not the children collection).

In the context of a larger set of dependent connections and data binding, forgetting that there is no guarantee about the lack of order can make for some real fun debugging!

So my lesson learned for the day: architect order dependency out of KVO (and therefore data binding) from the get go to avoid having to continually prune away subtle ordering bugs .

Ahh, good times!

About these ads
Published in: on May 11, 2009 at 5:45 pm  Comments (3)  
Tags: , ,

The URI to TrackBack this entry is: http://cocoaconvert.net/2009/05/11/self-observation/trackback/

RSS feed for comments on this post.

3 CommentsLeave a comment

  1. You have at least two options. You can introduce an intermediate object that holds one “child” and one “port” each, so that there’s no synchronization needed at all. The intermediate object can just be an NSDictionary. This is the most Cocoa-friendly approach.

    You can also implement KVC indexed accessors for either children or ports, and do whatever glue work you need to do in those methods. Indexed Accessors: http://bit.ly/15KNJk

    In general, an object should rarely (if ever) need to observe itself. Instead, it can just implement the methods that KVC looks for doing its normal method search.

    Great site. Thanks for writing.

    • Hi Scott,

      First off, thanks a ton for the great writing on your site! Definitely a heavily used resource of mine.

      The intermediate object idea is a cool little nugget, I appreciate the tip.

      I did end up implementing indexed accessors. Coming from the .NET world, I would often subscribe to my own event/notifications rather than customize my property get/set accessors for simplicity and brevity’s sake.

      Definitely one of those performance versus maintainability things, but like many (all?) lazy programmers I know, in most situations I wait for the profiler to force me into action.

      On a related note, is there something I’m missing about effectively implementing indexed accessors (especially the mutable kind)? Man are they veeeeeerbose!

      Thanks for the tips and kind words,

      Jeremy

  2. Another option, would be to use KVO internally. But after your object reorders its data, post a notification instead. Then any interested objects can observe your notification. That should keep you reasonably decoupled yet still control the order of which objects are notified.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: