在 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:
的含义不是“按照关键路径到达最后一个元素并观察倒数第二个对象上的该属性”。它是“观察接收者对象的这个关键路径”。关键路径总是被认为是从接收者开始的。
换句话说,对于您的第二个代码示例,它不是“观察self
的controller
的isEnabled
属性”(这是您第一个示例的含义)。意思是“观察self
的controller.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 中使用嵌套的键路径的主要内容,如果未能解决你的问题,请参考以下文章
swift 一个NSObject扩展,允许您使用枚举作为常见KVO任务的关键路径。