Search code examples
iosmodel-view-controllerkey-value-observing

KVO Loading a new view with existing model data


I've recently begun to discover what can be done with KVO and I'm refactoring some of my code and saving a lot of lines at the same time. I do face one issue that is so general that it makes me wonder whether a certain pattern is recommended.

In some cases I load a new view controller that needs to represent data from an already initialized model. On -viewDidLoad I would register for KVO:

[_model addObserver:self
         forKeyPath:kSomeKey
            options:NSKeyValueObservingOptionNew
            context:(__bridge void *)(_model)];

and change my interface when values change:

 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change 
                       context:(void *)context
{
if ([keyPath isEqual:kSomeKey] && context == (__bridge void *)(_model)) {

    [self updateSomeInterfaceElement];

}

Unfortunately and understandably, the view is not updated with current values from the model when I load my new view.

Is my best option to call -updateSomeInterfaceElement in -viewDidLoad? It doesn't seem to be a big deal like this, but when listening for 10-20 properties, it looks very inefficient (especially since all my -updateSomeInterfaceElement methods are mostly 1 line only, so no need to make them into a separate method). Is there any way to circumvent this, or is there a more elegant solution?


Solution

  • You want to change your options to include NSKeyValueObservingOptionInitial. This will cause KVO to fire a notification when you add the observer, providing the observer with the "initial" value.

    Also, as an aside, you should get in the habit of calling super if observeValueForKeyPath:... is called for a notification you didn't sign up for. Also, it's a bit more bulletproof to avoid using "live" pointers in the role of KVO contexts (since a future object could have the same pointer if the current object is deallocated.) I generally prefer to use a pattern like this:

    static void * const MyObservationContext = (void*)&MyObservationContext;
    
    - (void)viewDidLoad
    {
        // ... other stuff ...
        [self addObserver:self forKeyPath:@"model.someKey" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:MyObservationContext];
        // ... other stuff ...
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 
    {
        if (context == MyObservationContext)
        {
            // Do stuff
        }
        else
        {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        }
    }