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 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;
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 line
static char ClassNameKVOContext = 0;
When I begin observing the aspect
property on targetObject
(an instance of TargetClass
) I'll have
[targetObject addObserver:self
forKeyPath:PFXKeyTargetClassAspect
options://...
context:&ClassNameKVOContext];
where PFXKeyTargetClassAspect is an NSString *
defined in TargetClass.m
to be equal to @"aspect"
and declared extern
in TargetClass.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
on targetObject
I'll have
[targetObject removeObserver:self
forKeyPath:PFXKeyTargetClassAspect
context:&ClassNameKVOContext];
In order to avoid too much indentation in my implementation of -observeValueForKeyPath:ofObject:change:context:
, I like to write
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context != &ClassNameKVOContext) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}
if ([object isEqual:targetObject]) {
if ([keyPath isEqualToString:PFXKeyTargetClassAspect]) {
//targetObject has changed the value for the key @"aspect".
//do something about it
}
}
}
I think the better way would be to implement it as apple's doc says:
The address of a uniquely named static variable within your class makes a good context.
static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
see documentation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With