Best practices for context parameter in addObserver (KVO)

Crystal picture Crystal · Oct 4, 2012 · Viewed 16.1k times · Source

I was wondering what you should set the Context pointer in KVO when you are observing a property. I'm just starting to use KVO and I haven't gleaned too much from the documentation. I see on this page: http://www.jakeri.net/2009/12/custom-callout-bubble-in-mkmapview-final-solution/ the author does this:

[annView addObserver:self
forKeyPath:@"selected"
options:NSKeyValueObservingOptionNew
context:GMAP_ANNOTATION_SELECTED];

And then in the callback, does this:

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

NSString *action = (NSString*)context;


if([action isEqualToString:GMAP_ANNOTATION_SELECTED]){

I'm assuming in this scenario, the author just creates a string to be identified later in the callback.

Then in iOS 5 Pushing the Limits book, I see he does this:

[self.target addObserf:self forKeyPath:self.property options:0 context:(__bridge void *)self];

callback:

if ((__bridge id)context == self) {
}
else {
   [super observeValueForKeyPath .......];
}

I was wondering if there is a standard or best practices to pass into the context pointer?

Answer

ipmcc picture ipmcc · Jan 4, 2013

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;