Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
279 views
in Technique[技术] by (71.8m points)

objective c - What is the context parameter used for in Key value observing

What's the use of context parameter in following method which is used to register for key value notifications. The documentations just denotes it as arbitrary set of data.

addObserver:self forKeyPath:@"selectedIndex" options:NSKeyValueObservingOptionNew context:nil

Can somebody shed some light what's the purpose behind it ...

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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:

  1. You might be called twice when the view's center changes.
  2. 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];
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...