在 KVO 中使用嵌套的键路径

Posted

技术标签:

【中文标题】在 KVO 中使用嵌套的键路径【英文标题】:Using nested key paths in KVO 【发布时间】:2015-01-27 10:02:36 【问题描述】:

假设我想观察一个名为“isEnabled”的属性在一个名为“控制器”的自我上。 AFAIK 我有两种安装此类观察的选项: 1.[self.controller addObserver:self forKeyPath:@"isEnabled" options:0 context:nil]; 2.[self addObserver:self forKeyPath:@"controller.isEnabled" options:0 context:nil];

我注意到这两种方法之间的实际区别 - 在第二种方法中,如果 self 上的“控制器”对象被替换,我将收到通知,而在第一种方法中,只有当“isEnabled”属性为在我安装观察的同一个实例上进行了更改。

我的问题是,这到底是在哪里记录的?我知道它有效,但我应该使用它吗? 我在 Apple 文档中找不到任何关于这种行为的提及,尽管其他一些人在论坛中提到了它。任何参考都将被欣然接受。

谢谢。

【问题讨论】:

我几乎会认为它隐含地记录了获取关键 path 而不是仅使用 key 【参考方案1】:

这不仅仅是如果controller属性发生变化你会收到一个更改通知,而是KVO会切换到跟踪新控制器的isEnabled属性并停止跟踪旧控制器的isEnabled属性.

这隐含在关键路径的概念中。 Key-Value Observing 建立在 Key-Value Coding 之上。 Key-Value Coding Programming Guide 中提到了Key-Value Coding Fundamentals 中的关键路径:

键路径是一串点分隔键,用于指定要遍历的对象属性序列。序列中第一个键的属性是相对于接收者的,后续的每个键都是相对于前一个属性的值进行评估的。

例如,键路径address.street会从接收对象中获取address属性的值,然后确定street相对于address对象的属性。

-addObserver:forKeyPath:options:context: 的含义不是“按照关键路径到达最后一个元素并观察倒数第二个对象上的该属性”。它是“观察接收者对象的这个关键路径”。关键路径总是被认为是从接收者开始的。

换句话说,对于您的第二个代码示例,它不是“观察selfcontrollerisEnabled 属性”(这是您第一个示例的含义)。意思是“观察selfcontroller.isEnabled。任何时候评估表达式[self valueForKeyPath:@"controller.isEnabled"] 的结果已经或可能已经改变,通知我。”

我知道它有效,但我应该使用它吗?

是的,你应该使用它。这是 API 的预期含义。这就是为什么该方法将其参数描述为键 路径 而不仅仅是键。

【讨论】:

"这不仅仅是当controller属性发生变化时你会收到变更通知,而是KVO会切换到跟踪新控制器的isEnabled属性,并停止跟踪新控制器的isEnabled属性旧控制器”。感谢您澄清这个问题,但是我仍然对这没有在任何地方明确记录这一事实感到有些不舒服。【参考方案2】:

在第二种方法中,如果“控制器”,我将收到通知 自己的对象被替换了

我想说的是,在第二种方法中,如果控制器对象被替换或其isEnabled 属性更改,您将收到通知。换句话说,当controller.isEnabled 发生变化时(正如肯的回答所解释的那样)。

检查这个例子:

- (void)viewDidLoad 
    [super viewDidLoad];

    self.controller = [[ViewController2 alloc] init];

    [self.controller addObserver:self forKeyPath:@"isEnabled" options:NSKeyValueObservingOptionNew context:nil];
    [self addObserver:self forKeyPath:@"controller.isEnabled" options:NSKeyValueObservingOptionNew context:nil];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^
        self.controller.isEnabled = !self.controller.isEnabled;

        // replace controller
        [self.controller removeObserver:self forKeyPath:@"isEnabled"];
        self.controller = [[ViewController2 alloc] init];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^
            self.controller.isEnabled = !self.controller.isEnabled;
        );
    );


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

    NSLog(@"KVO %d %@ %p", self.controller.isEnabled, keyPath, self.controller);

我们将收到 4 条 KVO 通知:

KVO 1 controller.isEnabled 0x7fbbc2e4b4e0 <-- These 2 fire together when we toggle isEnbled
KVO 1 isEnabled 0x7fbbc2e4b4e0            <-- So basically 1. and 2. behave the same
KVO 0 controller.isEnabled 0x7fbbc2e58d30 <---- controller was replaced
KVO 1 controller.isEnabled 0x7fbbc2e58d30 <---- Toggle on the new instance

【讨论】:

以上是关于在 KVO 中使用嵌套的键路径的主要内容,如果未能解决你的问题,请参考以下文章

依赖键路径的KVO对Swift类不起作用

swift 一个NSObject扩展,允许您使用枚举作为常见KVO任务的关键路径。

KVC与KVO

快速,在具有复合谓词的提取结果控制器中找不到实体的键路径

Vue Router 嵌套路径会在手动刷新时破坏静态资产路径

在WPF中,如何使用XAML指定嵌套在目录中的文件的路径?