Joshua
Joshua

Reputation: 15520

Help with Key-Value-Observing

I need a bit of help with KVO, I'm about half way there. What I'm trying to do is trigger a method when something in an Tree Controller changes.

So I 'm using this code to register as a KVO.

[theObject addObserver: self
            forKeyPath: @"myKeyPath"
               options: NSKeyValueObservingOptionNew
               context: NULL];

But how do i trigger a method when the Key Path I am observing changes?

One extra question, when I add my self as an Observer I want the Key Path to be a Property in my Core Data model, Have I done that correctly?

Upvotes: 2

Views: 2302

Answers (4)

cannyboy
cannyboy

Reputation: 24426

( this is a technique I learned here: http://www.bit-101.com/blog/?p=1999 )

You could pass the method in the 'context', like

[theObject addObserver:self 
            forKeyPath:@"myKeyPath"
               options:NSKeyValueObservingOptionNew
               context:@selector(doSomething)];

..then in the observeValueForKeyPath method, you cast it to the SEL selector type, and then perform it.

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    SEL selector = (SEL)context;
    [self performSelector:selector];
}

If you want to pass data to the doSomething method you could use the 'new' key in the 'change' dictionary, like this:

[theObject addObserver:self 
              forKeyPath:@"myKeyPath"
                 options:NSKeyValueObservingOptionNew
                 context:@selector(doSomething:)]; // note the colon

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

        // send the new value of observed keyPath to the method
        [self performSelector:selector withObject:[change valueForKey:@"new"]]; 
    }


-(void)doSomething:(NSString *)newString // assuming it's a string
{
      label.text = newString;
}

Upvotes: 3

outis
outis

Reputation: 77450

Override observeValueForKeyPath:ofObject:change:context: to dispatch the method you wish to call.

@interface Foo : NSObject {
    NSDictionary *dispatch;
    ...
}
@end
@implementation Foo
-(id)init {
    if (self = [super init]) {
        dispatch = [[NSDictionary dictionaryWithObjectsAndKeys:NSStringFromSelector(@selector(somethingHappenedTo:with:)),@"myKeyPath",...,nil] retain];
        ...
    }
}
...
- (void)observeValueForKeyPath:(NSString *)keyPath
            ofObject:(id)object
            change:(NSDictionary *)change
            context:(void *)context
{
    SEL msg = NSSelectorFromString([dispatch objectForKey:keyPath]);
    if (msg) {
        [self performSelector:msg withObject:object withObject:keyPath];
    }
    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
...

See "Receiving Notification of a Change" for details.

Upvotes: 6

Barry Wark
Barry Wark

Reputation: 107774

I would recommend you take a look at the Google Toolbox For Mac's GTMNSObject+KeyValueObserving.h category or at least the blog post by Michael Ash that inspired it. Basically, doing manual KVO right is very subtle and the pattern suggested by the API is not ideal. It's much better to put an other layer on the API (as GTMNSObject+KeyValueObserving) does that makes things more like the NSNotification API and hides some of the sources of subtle bugs.

Using GTMNSObject+KeyValueObserving, you would do

[theObject gtm_addObserver:self
                forKeyPath:@"myKeyPath"
                  selector:@selector(myCallbackSelector:)
                  userInfo:nil
                   options:NSKeyValueObservingOptionNew];

and your -myCallbackSelector: will get called when the value at @"myKeyPath" changes with an argument of type GTMKeyValueChangeNotification, which encapsulates all the relevant information you might need.

This way, you don't have to have a big dispatch table in observeValueForKeyPath:ofObject:change:context (in reality one is maintained for you by the category) or have to worry about the correct way to use the context pointer to avoid conflict with super/sub classes etc.

Upvotes: 5

nall
nall

Reputation: 16149

You should implement this and it will be invoked when the keypath changes:

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

More info here.

Upvotes: 4

Related Questions