Key-Value Observing

KVO is a simple addition to KVC. When you write to an object using a key or key path, it is of course Cocoa doing the look-up — and therefore Cocoa can spy on (“Observe”) all changes to objects that use KVC. Cocoa shares this wiretap intelligence with you, if you ask.

KVO is simply a mechanism whereby one object registers an interest in another object, and is automatically notified by Cocoa whenever a change is made. The method is addObserver:forKeyPath:options:context:; you call it on the instance you wish to observe, and when a change does occur, you’ll receive an observeValueForKeyPath:ofObject:change:context: callback. I’m not going to go into detail on these — check out the official KVO documentation.

Bear in mind, changes that don’t use KVC will slip through the net; if you do modify a KVC’d value directly, you should notify Cocoa, which will then pass it on to any observers. You do this by calling willChangeValueForKey: beforehand and didChangeValueForKey: afterwards, on the object whose value is being modified.

You can also tell Cocoa that keys are interrelated; this is really only useful for keys that map to functions, not values. If your Colour class has a function getBrightness that converts the red-green-blue colour into hue-saturation-brightness colour-space and returns the brightness, you can register the brightness key path as dependent on red, green and blue, and any objects observing brightness will be notified to fetch the new value whenever any of the RGB components changes.

In OS X 10.4, the call was setKeys:triggerChangeNotificationsForDependentKey: (be aware that this is a class method, not an instance method, and you need to call it early in your program’s lifetime), but its been superseded by a new technique in Leopard.

The new hotness is for your class to define a keyPathsForValuesAffectingValueForKey: method, which returns an NSSet with the names of the key-paths that change whichever key Cocoa passed in. You should always call [super keyPathsForValuesAffectingValueForKey: key] and merge the results with your own.

There’s also a short-cut of sorts, whereby you can simply define (for example) a keyPathsForValuesAffectingBrightness method and have it return the appropriate set.

A couple of notes on the limitations of KVO:

  • Any object can observe members of any other object, but dependencies — Tiger-style or Leopard-style — only seem to work within one instance. You can’t get Cocoa to link changes to members of one instance to those of another automagically. If you need this, you’ll need to manually register for change notifications of one of the keys using the previously-mentioned addObserver:forKeyPath:options:context and trigger the change notification on the other yourself.
  • When dealing with collections such as arrays or dictionaries, it’s important not to get confused between changes to the collection, and changes to its members. If you Observe sky.colours, an array, you’ll get notifications when items are added or removed from the array — but not if the properties of those items change. To do that, you need to Observe the properties with the keypaths,, etc (or perhaps reduce your effort by defining a colour member which returns the complete colour information as an NSColor, and Observe that.)

So far, I’ve not used KVO an awful lot myself directly, except for notifying it of changes I’m making. However, it’s the underpinning of Cocoa Bindings, which we’ll look at soon, as they’re extremely useful and at the heart of a lot of modern Cocoa applications.