iPhone KVO between two classes

fmcauley picture fmcauley · Nov 10, 2010 · Viewed 17.8k times · Source

I have two classes in my app class A and Class B. Both class A and B are instances of UIViewController. Class A has a button that when pushed pushes class B onto the stack. Class B has a string that class A would like to observe and update it's interface with as needed. I've been able to use: [self addObserver:self forKeyPath:@"name" options:0 context:NULL]; in class B to view the changes to the string. When i try and use the following in class A viewWillAppear method:

ClassB *b = [[ClassB alloc]init];
[b addObserver:self forKeyPath:@"name" options:0 context:NULL];

and add the method:

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

No action is fired when trying to view the updates made in B from A. I feel silly asking this question but how does KVO work between two classes in iOS? I know this should work.

Answer

nekno picture nekno · Nov 10, 2010

You can observe changes across different objects/classes. I think the problem is in the options parameter of addObserver:forKeyPath:options:context:.

There are various options for the type of observing you want to do. The KVO Guide is a good starting point, but you probably want NSKeyValueObservingOptionNew, which I use in the example below.

First, "name" should be a public property in ClassB.

Second, you probably don't need to add the observer to "b" in viewWillAppear in ClassA, because you don't need to add it everytime the ClassA view is going to appear. You just need to add the observer once, when you create the ClassB view. Once you've added the observer, the observeValueForKeyPath:ofObject:change:context: method will be executed in ClassA, so you can do the update to the ClassA UI from there. You shouldn't need to do anything every time ClassA is about to appear.

In Class A, you should probably create ClassB just before you are going to push ClassB onto the controller stack, presumably in the event handler for some action the user took. Immediately after you create ClassB, add the observer in ClassA with the correct NSKeyValueObservingOption value.

If you just want to be notified whenever the public property "name" in ClassB is changed, then try this:

ClassB

@interface ClassB : UIViewController {
}

@property (retain) NSString* name;

- (void) aMethodThatModifiesName:(NSString*)newName;

@end


@implementation ClassB 

@synthesize name;

- (void) aMethodThatModifiesName:(NSString*)newName {
    // do some stuff
    self.name = newName;
}

@end

ClassA

@interface ClassA : UIViewController {
}

@property (nonatomic, retain) IBOutlet UILabel* nameLabel;

- (IBAction) someEventHandler:(id)sender;

@end

@implementation ClassA

- (IBAction) someEventHandler:(id)sender {
    ClassB* b = [[ClassB alloc]init];
    [b addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
    [self.navigationController pushViewController:b animated:YES];
    [b release];
}

- (void) observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
    if ([keyPath isEqual:@"name"]) {
        NSString* changedName = [change objectForKey:NSKeyValueChangeNewKey];
        // do something with the changedName - call a method or update the UI here
        self.nameLabel.text = changedName;
    }
}

@end