我遇到了一些涉及NSTreeController
和KVO的装腔作势。 NSTreeController
selectionIndexPaths
属性被记录为KVO可观察的 - 当我直接观察它时,它完美地运作。但是,如果我将NSTreeController
' s selectionIndexPath
列为某些其他媒体资源的依赖关系,然后尝试观察 ,那么通知时不会触发通知期望的。
这是我能提出的最简短的示例代码来证明我的意思:
import Cocoa
class ViewController: NSViewController {
// Our tree controller
@IBOutlet dynamic var treeController: NSTreeController!
// Some random property on my object; you'll see why it's here later
@objc dynamic var foo: String = "Foo"
// A quick-and-dirty class to give us something to populate our tree with
class Thingy: NSObject {
@objc let name: String
init(_ name: String) { self.name = name }
@objc var children: [Thingy] { return [] }
}
// The property that the tree controller's `Content Array` is bound to
@objc dynamic var thingies: [Thingy] = [Thingy("Foo"), Thingy("Bar")]
// Dependencies for selectionIndexPaths
@objc private static let keyPathsForValuesAffectingSelectionIndexPaths: Set<String> = [
#keyPath(treeController.selectionIndexPaths),
#keyPath(foo)
]
// This property should be dependent on the tree controller's selectionIndexPaths
// (and also on foo)
@objc dynamic var selectionIndexPaths: [IndexPath] {
return self.treeController.selectionIndexPaths
}
// Some properties to store our KVO observations
var observer1: NSKeyValueObservation? = nil
var observer2: NSKeyValueObservation? = nil
// And set up the observations
override func viewDidLoad() {
super.viewDidLoad()
self.observer1 = self.observe(\.selectionIndexPaths) { _, _ in
print("This is only logged when foo changes")
}
self.observer2 = self.observe(\.treeController.selectionIndexPaths) { _, _ in
print("This, however, is logged when the tree controller's selection changes")
}
}
// A button is wired to this; its purpose is to set off the
// KVO notifications for foo
@IBAction func changeFoo(_: Any?) {
self.foo = "Bar"
}
}
此外,故事板中还完成了以下设置:
treeController
插座连接到它。thingies
。children
。arrangedObjects
和selectionIndexPaths
。changeFoo:
方法。如果您想自己尝试一下,我已经上传了一个示例项目here。
行为如下:
只要大纲视图(以及树控制器的)选择发生变化,就会一直触发observer2
的通知。
但是,当大纲视图的选择发生变化时,observer1
的通知不会。
但是,单击按钮时会触发observer1
的通知 ,并且foo
已更改。这表明属性的依赖正在考虑,但不是针对这一个特定的关键路径。
使用带有observeValue(forKeyPath:bla:bla:bla:)
覆盖的旧式方法而不是swank 4 Swift 4基于闭包的系统似乎表现相同。
编辑:嗯,这不是斯威夫特的错!当我在Objective-C中编写这个程序时,会发生同样的事情:
@interface Thingy: NSObject
@property (nonatomic, copy) NSString *name;
- (instancetype)initWithName:(NSString *)name;
@end
@implementation Thingy
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self == nil) {
return nil;
}
self->_name = name;
return self;
}
- (NSArray *)children { return @[]; }
@end
void *ctxt1 = &ctxt1;
void *ctxt2 = &ctxt2;
@interface ViewController()
@property (nonatomic, strong) IBOutlet NSTreeController *treeController;
@property (nonatomic, copy) NSString *foo;
@property (nonatomic, copy) NSArray *thingies;
@end
@implementation ViewController
+ (NSSet *)keyPathsForValuesAffectingSelectionIndexPaths {
return [NSSet setWithObjects:@"treeController.selectionIndexPaths", @"foo", nil];
}
- (NSArray *)selectionIndexPaths {
return self.treeController.selectionIndexPaths;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.thingies = @[[[Thingy alloc] initWithName:@"Foo"], [[Thingy alloc] initWithName:@"Bar"]];
[self addObserver:self forKeyPath:@"selectionIndexPaths" options:0 context:ctxt1];
[self addObserver:self forKeyPath:@"treeController.selectionIndexPaths" options:0 context:ctxt2];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == ctxt1) {
NSLog(@"This only gets logged when I click the button");
} else if (context == ctxt2) {
NSLog(@"This gets logged whenever the selection changes");
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (IBAction)changeFoo:(__unused id)sender {
self.foo = @"Bar";
}
@end
我已经盯着这一段时间了,我无法弄清楚为什么直接观察treeController.selectionIndexPaths
有效,但观察依赖于treeController.selectionIndexPaths
的属性却没有。而且,由于我一般觉得我对KVO及其工作方式有很好的处理能力,因此我无法解释这一点。
有谁知道造成这种差异的原因?
谢谢!