NSTreeController KVO通知意外未触发

时间:2017-11-14 01:17:27

标签: objective-c swift key-value-observing key-value-coding nstreecontroller

我遇到了一些涉及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插座连接到它。
  • 绑定树控制器&#34;内容数组&#34;绑定到视图控制器上的thingies
  • 设置树形控制器&#34; Children Key Path&#34;到children
  • 创建大纲视图,并绑定其内容&#34;和#34;选择索引路径&#34;树控制器上分别绑定到arrangedObjectsselectionIndexPaths
  • 创建一个按钮,并将其指向视图控制器的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及其工作方式有很好的处理能力,因此我无法解释这一点。

有谁知道造成这种差异的原因?

谢谢!

0 个答案:

没有答案