Best practices for context parameter in addObserver (KVO)

The important thing is (generally speaking) that you use something (as opposed to nothing) and that whatever you use be unique and private to your use of it.

The primary pitfall here happens when you have an observation in one of your classes, and then someone subclasses your class, and they add another observation of the same observed object and the same keyPath. If your original observeValueForKeyPath:... implementation only checked keyPath, or the observed object, or even both, that might not be sufficient to know that it’s your observation being called back. Using a context whose value is unique and private to you allows you to be much more certain that a given call to observeValueForKeyPath:... is the call you’re expecting it to be.

This would matter if, for instance, you registered only for didChange notifications, but a subclass registers for the same object and keyPath with the NSKeyValueObservingOptionPrior option. If you weren’t filtering calls to observeValueForKeyPath:... using a context (or checking the change dictionary), your handler would execute multiple times, when you only expected it to execute once. It’s not hard to imagine how this might cause problems.

The pattern I use is:

static void * const MyClassKVOContext = (void*)&MyClassKVOContext;

This pointer will point to its own location, and that location is unique (no other static or global variable can have this address, nor can any heap or stack allocated object ever have this address — it’s a pretty strong, although admittedly not absolute, guarantee), thanks to the linker. The const makes it so that the compiler will warn us if we ever try to write code that would change the value of the pointer, and lastly, static makes it private to this file, so no one outside this file can obtain a reference to it (again, making it more likely to avoid collisions).

One pattern I would specifically caution against using is one that appeared in the question:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    NSString *action = (NSString*)context;
    if([action isEqualToString:GMAP_ANNOTATION_SELECTED]) {

context is declared to be a void*, meaning that that’s all the guarantee that can be made about what it is. By casting it to an NSString* you’re opening a big box of potential badness. If someone else happens to have a registration that doesn’t use an NSString* for the context parameter, this approach will crash when you pass the non-object value to isEqualToString:. Pointer equality (or alternatively intptr_t or uintptr_t equality) are the only safe checks that can be used with a context value.

Using self as a context is a common approach. It’s better than nothing, but has much weaker uniquing and privacy, since other objects (not to mention subclasses) have access to the value of self and might use it as a context (causing ambiguity), unlike with the approach I suggested above.

Also remember, it’s not just subclasses that might cause pitfalls here; Although it’s arguably a rare pattern, there’s nothing that preventing another object from registering your object for new KVO observations.

For improved readability, you could also wrap this up in a preprocessor macro like:

#define MyKVOContext(A) static void * const A = (void*)&A;

Leave a Comment