views:

428

answers:

4

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?

+4  A: 

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.

nall
+4  A: 

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.

outis
"NSDictionary dispatch;" should be "NSDictionary *dispatch;", but otherwise good.
BJ Homer
D'oh. Fixed. Also changed "performSelector" to "performSelector:withObject:withObject", for a more generally useful approach.
outis
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?
Joshua
Thanks, So far it works!
Joshua
@Joshua: the property name sounds like the correct key path, for common applications of KVO. If you're doing something uncommon, however...
outis
I see, I don't get any errors but it the selector doesn't work. Here's a Picture of the code … http://snapplr.com/0q5f
Joshua
Does `dispatch` have an entry for each key path you're observing? I updated the example to include this.
outis
I believe you want to wrap those selectors in NSSelectorFromString and NSStringFromSelector. I don't think the above will compile, not w/o warnings anyway. and we all use -Wall, right? ;)
nall
Thanks for the help. I have updated my code by wrapping the selector with NSSelectorFromString but I still have some warnings. http://snapplr.com/aeh5
Joshua
Duh, SELs aren't objects. Anyone know why not? Nevermind... this *is* SO, so I'll just post a new question.
outis
+4  A: 

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.

Barry Wark
That looks interesting, where do I download the code/framework?
Joshua
http://code.google.com/p/google-toolbox-for-mac/GTM is, like the boost C++ libraries, a take-what-you-want architecture. They recommend that you include the files/classes you want directly into your own project. There are only a couple of common includes.
Barry Wark
A: 

( 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;
}
cannyboy