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?
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 checkedkeyPath
, or the observedobject
, or even both, that might not be sufficient to know that it's your observation being called back. Using acontext
whose value is unique and private to you allows you to be much more certain that a given call toobserveValueForKeyPath:...
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 theNSKeyValueObservingOptionPrior
option. If you weren't filtering calls toobserveValueForKeyPath:...
using acontext
(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:
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:
context
is declared to be avoid*
, meaning that that's all the guarantee that can be made about what it is. By casting it to anNSString*
you're opening a big box of potential badness. If someone else happens to have a registration that doesn't use anNSString*
for thecontext
parameter, this approach will crash when you pass the non-object value toisEqualToString:
. Pointer equality (or alternativelyintptr_t
oruintptr_t
equality) are the only safe checks that can be used with acontext
value.Using
self
as acontext
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 ofself
and might use it as acontext
(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:
I think the better way would be to implement it as apple's doc sais:
see documentation.
The KVO context should be a pointer to a static variable, as this gist demonstrates. Typically, I find myself doing the following:
Near the top of my file
ClassName.m
I'll have the lineWhen I begin observing the
aspect
property ontargetObject
(an instance ofTargetClass
) I'll havewhere PFXKeyTargetClassAspect is an
NSString *
defined inTargetClass.m
to be equal to@"aspect"
and declaredextern
inTargetClass.h
. (Of course PFX is just a placeholder for the prefix you're using in your project.) This gives me the advantage of autocomplete and protects me from typos.When I finish observing
aspect
ontargetObject
I'll haveIn order to avoid too much indentation in my implementation of
-observeValueForKeyPath:ofObject:change:context:
, I like to write