Skip to content

Key Value Observing

sebastienwindal edited this page Dec 6, 2012 · 14 revisions

Key value observing allows for an object to observe changes to any object public properties. The observed object does not know it is observed and there is nothing to implement to make an object observable, as long as it has properties, it can be observed...

The observer registers for each properties it is interested in observing using addObserver:forKeyPath:options:context. When the specified properties is changed, observeValueForKeyPath:ofObject:change:context: is called.

notes

Less coupled than delegation, BigCalculator does not know anything at all about the object that is observing it (UIViewController). The observer needs to have a reference to the observee at some point to start observing it.

Pros

  • powerful and flexible
  • less coupled
  • absolutely nothing to do in the object observed as long as it has the right properties. You can observe objects you cannot change (third party framework object, etc...)
  • More than one object can observe the same object.
  • asynchronous

It works very well for Model -> View Controller(s) scenario.

Cons

  • with great power comes great responsiility, the responsibility to unsubscribe!
  • You register using the name of the property in a NSString. Potential for breakage, changing the property name will break your observer code. At least it will not crash at runtime, but you won't get notified.
  • Performance. KVO observing may not be the right choice for objects that are changed "a lot" (e.g. using KVO to notify of touch position during drawing, or to notify of the position of a dragged item on a view, or to notify how many bytes we have left during a media download operation are probably a bad idea (I have not tried)). Performance issues may occur in at least two places:
    • Boxing/unboxing. KVO sends modified values in the change NSDictionary which can only contain objects. Struct and int/float, etc... will have to be boxed/unboxed to/from NSNumber objects, etc...
    • You are actually not just notified when a property changes, you are notified when the setter of the property is called even if it does result in a change of the property value.
    • It is not possible to have any coallescing logic when changin multiple properties in one shot, you will get one notification call per property changed. Note that the last two items can someone been improved by implementing manual KVO notification, which of course adds complexity. See docs for more details.
  • It is a one way communication, publisher does not know or care about the observer(s).

Gotchas and best practices

working with arrays, dictionaries, etc...

By default adding/removing an element to an element to a NSMutableArray, NSMutableDictionary, or a NSMutableSet will not trigger the notification. When using KVO, it is preferable to use the immutable versions (NSArray, NSDictionary, etc...).

This will trigger a notification self.myArray = [self.myArray arrayByAddingObject:myNewObject];

This won't self.myMutableArray = [self.myMutableArray addObject:myNewObject];

One might argue it is good practice anyways to use immutable structures for any publicly exposed properties... Think about this:

  • NSArray is threadsafe, NSMutableArray is not.
  • You can iterate safely an NSArray, not an NSMutableArray. If a mutable array is modified while being walked (by adding or removing an element) you will most likely crash.

IMO, for publicly exposed arrays, in 99% of the cases, the added complexity that an NSMutableArray brings with is not worth the performance improvement it provides. After doing some benchmarking and used fairly big arrays in some of my app, and the rule of thumb I came up with: less than 1000 elements in my array, immutable NSArrays, over that start to think about being smarter...

Don't forget to unsubscribe!

Failure to unsubscribe will most likely result in a dealloced observer object being notified when something changed in the observee. Messages are sent to a junk object, strange unpredictable and hard to debug crashes, memory corruption, useless stack trace, etc...

It is important that removeObserver:forKeyPath: is called in your dealloc for each of the properties you subscribed to.

A technique that works quite well is to handle registration/unregistration in the setter of the object you want to observe using KVO. Example:

in the .h file:

@property (nonatomic, strong) SomeModelObjectIWantToObseve *model;

in the .m file:

@synthesize model = _model;

-(void) setModel:(SomeModelObjectIWantToObserve *)model
{
    if (_model == model) return;

    // stop observing old model
    [_model removeObserver:self forKeyPath:@"propertyName1"];
    [_model removeObserver:self forKeyPath:@"propertyName2"];
    ...
    // save new value in ivar
    _model = model;

    // start observing new model
    [_model addObserver:self forKeyPath:@"propertyName1" options:xxx context:NULL];
    [_model addObserver:self forKeyPath:@"propertyName2" options:xxx context:NULL];
    ...
}

Then in dealloc, unregsiter simply by resetting the model property:

-(void) dealloc
{
    self.model = nil;
}

Switch to main thread if needed!

The notification callback executes in the same thread the property was changed.

Back