Search code examples
objective-ccocoabindingkey-value-observing

Why is this code not KVO-Compliant?


I have a NSViewController subclass with a convenience method to return the window controller like this

@interface ILViewController : NSViewController

- (ILWindowController *)windowController;

@end

@implementation ILViewController

- (NSWindowController *)windowController {
    return self.view.window.windowController;
}

- (void)setView:(NSView *)view {
    [self willChangeValueForKey:@"windowController"];
    [super setView:view];
    [self didChangeValueForKey:@"windowController"];
}

@end

The window controller has a NSString * property named mainStatus which I'm trying to bind to through the view controller. However, I am getting this error at run time.

Cannot update for observer <NSAutounbinderObservance 0x1005cf990> for the key path 
"windowController.mainStatus" from <ILViewController 0x1001976b0>, most likely because
the value for the key "windowController" has changed without an appropriate KVO
notification being sent. Check the KVO-compliance of the ILViewController class.

Why is this happening? I have appropriately sent the KVO notifications.

The following code, which uses an ivar and is actually NOT KVO compliant turns out to NOT result in any error....

@interface ILViewController : NSViewController {
    ILWindowController *_windowController;
}

- (ILWindowController *)windowController;

@end

@implementation ILViewController

- (NSWindowController *)windowController {
    return _windowController;
}

- (void)setView:(NSView *)view {
    [super setView:view];
    _windowController = view.window.windowController;
}

@end

This is confusing the heck out of me... Can someone see why the first implementation is not KVO compliant?

EDIT

Got it, if the view is added to window after it is set for the ViewController then the view's window is indeed changed without appropriate KVO notification. However, when I try to observe the view's window's window controller, I'm getting errors like this

KVO autonotifying only supports -set<Key>: methods that return void. Autonotifying 
will not be done for invocations of -[NSView _setWindow:].
Cannot remove an observer <NSKeyValueObservance 0x102e17880> for the key path
"window.windowController.mainStatus" from <NSView 0x102e13ec0>, most likely because
the value for the key "window" has changed without an appropriate KVO notification
 being sent. Check the KVO-compliance of the NSView class.

So if you can't observe a view's window, surely there's gotta be some way to be notified when the view's window changes. Does anyone know how?

Also, these still doesn't explain why the second version of the code that uses an iVar without KVO notification actually work.


Solution

  • If you set the view, and then add the view to a window, then windowController will have changed without KVO notifications being sent out. You need to observer your own view's window and send out will/did change notifications when that changes (It's possible the automatic key path dependence stuff does this for you, I don't remember if it actually works with paths or just values). Similarly if the window's windowController changes then you'll have the same problem.

    Edit: The method is called -automaticallyNotifiesObserversForKey:. I'm inclined to say that it doesn't support key paths, so you'll have to do the manual observing/notifying yourself.