I hope this explanation isn't too abstract to understand.
Suppose you create a class MyViewController
, which is a subclass of UIViewController
. You don't have the source code of UIViewController
.
Now you decide to make MyViewController
use KVO to observe changes to the center
property of self.view
. So you duly add yourself as an observer:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.view addObserver:self forKeyPath:@"center" options:0 context:NULL];
}
- (void)viewDidDisappear:(BOOL)animated {
[self.view removeObserver:self forKeyPath:@"center"];
[super viewDidDisappear:animated];
}
The problem here is that you don't know if UIViewController
also registers itself as an observer of self.view
's center
. If it does, then you might have two problems:
- You might be called twice when the view's center changes.
- When you remove yourself as an observer, you might also remove
UIViewController
's KVO registration.
You need a way to register yourself as an observer that is distinguishable from UIViewController
's KVO registration. That's where the context
argument comes in. You need to pass a value for context
that you are absolutely sure UIViewController
is not using as the context
argument. When you unregister, you use the same context
again so that you only remove your registration, not UIViewController
's registration. And in your observeValueForKeyPath:ofObject:change:context:
method, you need to check the context
to see if the message is for you, or for your superclass.
One way to be sure you use a context
that nothing else uses is to create a static
variable in MyViewController.m
. Use it when you register and unregister, like this:
static int kCenterContext;
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.view addObserver:self forKeyPath:@"center" options:0 context:&kCenterContext];
}
- (void)viewDidDisappear:(BOOL)animated {
[self.view removeObserver:self forKeyPath:@"center" context:&kCenterContext];
[super viewDidDisappear:animated];
}
Then in your observeValueForKeyPath:...
method, check it like this:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if (context == &kCenterContext) {
// This message is for me. Handle it.
[self viewCenterDidChange];
// Do not pass it on to super!
} else {
// This message is not for me; pass it on to super.
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
}
}
Now you're guaranteed not to interfere with your superclass's KVO, if it does any. And if somebody makes a subclass of MyViewController
that also uses KVO, it won't interfere with your KVO.
Note too that you can use a different context for each key path you observe. Then, when the system notifies you of a change, you can check the context instead of checking the key path. Testing for pointer equality is a little faster than checking for string equality. Example:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if (context == &kCenterContext) {
[self viewCenterDidChange];
// Do not pass it on to super!
} else if (context == &kBackgroundColorContext) {
[self viewBackgroundDidChange];
// Do not pass it on to super!
} else if (context == &kAlphaContext) {
[self viewAlphaDidChange];
// Do not pass it on to super!
} else {
// This message is not for me; pass it on to super.
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
}
}