Search code examples
objective-ckey-value-observing

Is it safe to observe a keyPath when an object along the path may change?


Is it safe to implement KVO as such:

[self addObserver:self
       forKeyPath:@"restaurant.oldestOrder.patron.frustrationLevel"
          options:0 context:nil];

…when you know that oldestOrder might change or become nil, and that previous oldestOrders might become deallocated?


Solution

  • Executive summary:

    You never need to add an observer twice for the same key just because keys in the middle changed, NSKeyValueObserving does this for you.

    As long as your -oldestOrder method doesn't return a dangling pointer, nothing should break.


    The way KVO works is when you add yourself as an observer, the observer is added to a list on the observed object, in the form of an NSObservationInfo object. Easy. An NSObservationInfo object is just a raw key (not a path), an object to watch, an observer to call, a pointer to the context you passed, and some other bookkeeping stuff.

    When you observe a path of keys, the observer relationships are observed through respective NSObservationInfo objects, which are held in the list on the called object, as if the whole path was a property of the object you called -addObserver on.

    In your example self observes restaurant, but the addObserver: method then creates NSObservationInfo objects in the background that are added as observers for each respective object down the path, and these are also added to your self's list of observers.

    When you observe @"A.B.C.D" of an object, it creates four NSObservationInfo objects, one for each key relation, and these are added to each key on the path as an observer, so you get an NSObservationInfo object watching A of the called, one watching B of A, one watching C of B, etc. When one of these NSObservationInfo objects observes a change, it passes this along to the original -addObserver caller as a change to the entire keypath.

    (This is an implementation detail but it should help the explanation.)

    When an object in the middle of the path is nil'd or removed, -willChangeValueForKey notices this and dutifully removes the NSObservationInfo objects as observers, along the path after the nil'd object. These NSObservationInfo objects still exist though, and they still know what keys they want to see, and when -didChangeValueForKey is called, it will look at the new value for the key and if it has key names matching the ones that it's chain of NSObservationInfo objects is looking for, it will attach them as the new observers.

    So, when any element along the path @"restaurant.oldestOrder.patron.frustrationLevel" changes, you will get a change notification, but the NSKeyValueObserving machinery will attempt to reestablish a relationship with the key at the end of that path after the change, and any subsequent change.

    Here's Cocoatron's implementation of this, you can see how it works yourself.