Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

KVO: +keyPathsForValuesAffecting<Key> doesn't work with (subclass of) NSObjectController

I have a KVO-able class (call it Observee), which affectedValue dynamic property is affected by affectingValue property. The dependency between the properties is defined by implementing +keyPathsForValuesAffectingAffectedValue method.

Setting a value to affectingValue notifies that affectedValue has changed as I expected, unless Ovservee is a subclass of NSObjectController. Full example follows:

@interface Observee : NSObject // or NSObjectController
@property (readonly, strong, nonatomic) id affectedValue;
@property (strong, nonatomic) id affectingValue;
@property (strong, nonatomic) NSArrayController *arrayController;
@end

@implementation Observee

@dynamic affectedValue;
- (id)affectedValue { return nil; }

+ (NSSet *)keyPathsForValuesAffectingAffectedValue {
  NSLog(@"keyPathsForValuesAffectingAffectedValue called");
  return [NSSet setWithObject:@"affectingValue"];
}

@end

@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (strong, nonatomic) Observee *observee;
@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)notification {
  self.observee = [[Observee alloc] init];
  [self.observee addObserver:self
                  forKeyPath:@"affectedValue"
                     options:NSKeyValueObservingOptionNew
                     context:NULL];
  NSLog(@"setting value to affectingValue");
  self.observee.affectingValue = @42;
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
  NSLog(@"affected key path = %@", keyPath);
}

@end

The example works fine and outputs as the following when Observee derives NSObject:

keyPathsForValuesAffectingAffectedValue called
setting value to affectingValue
affected key path = affectedValue

but when Observee derives NSObjectController:

keyPathsForValuesAffectingAffectedValue called
setting value to affectingValue

(note that "affected key path = affectedValue" is absent.)

It seems that keyPathsForValuesAffectingAffectedValue is called in both cases but it is no-op in the latter.

Also, any key paths involving an instance of (subclass of) NSObjectController won't affect other key paths, such as:

@implementation SomeObject

// `someValue` won't be affected by `key.path.(snip).arrangedObjects`
+ (NSSet *)keyPathsForValuesAffectingSomeValue {
  return [NSSet setWithObject:@"key.path.involving.anNSArrayController.arrangedObjects"];
}

@end

How do I declare dependency between key paths in such cases? And, why is this whole thing happening?

(Yes, I know about will/didChangeValueForKey: and friends, but wrapping up every affecting key path with a(nother) setter is terrible and I'd like to avoid it.)

like image 842
uasi Avatar asked Apr 12 '13 08:04

uasi


1 Answers

NSController and its subclasses are full of KVO "black magic" and unexpected behaviors. (For another example, they don't respect certain KVO options like NSKeyValueObservingOptionPrior) You will be disappointed if you expect them to behave like "normal" objects with respect to KVO. They exist primarily to support Cocoa bindings. Although at first glance bindings may look like mere syntactic sugar on top of KVO, you can see (by overriding the KVO supporting methods of a bound-to object and setting breakpoints in them) that there's actually quite a bit more going on underneath the covers than a simple KVO observation.

Please file bugs with Apple to increase the likelihood that they'll fix (or at least document) these sorts of issues/behavior.

like image 189
ipmcc Avatar answered Sep 23 '22 06:09

ipmcc