KVC 和 KVO(三)

877 阅读4分钟

欢迎大家关注我的公众号,我会定期分享一些我在项目中遇到问题的解决办法和一些iOS实用的技巧,现阶段主要是整理出一些基础的知识记录下来



文章也会同步更新到我的博客:
ppsheep.com

KVO和线程

KVO 行为是同步的 并且发生与所观察的值发生变化的同样的线程上。这听起来有点拗口,简单点说,就是监听行为发生的线程和所观察的值发生变化的线程,肯定是同一个线程,这样我们使用的时候就需要注意了:

当我们试图从其他线程改变属性值的时候我们应当十分小心,除非能确定所有的观察者都用线程安全的方法处理 KVO 通知

通常来说,我们不推荐把 KVO 和多线程混起来。如果我们要用多个队列和线程,我们不应该在它们互相之间用 KVO。

KVC

最简单的 KVC 能让我们通过以下的形式访问属性:

@property (nonatomic, copy) NSString *name;

取值:

NSString *n = [object valueForKey:@"name"]

设定:

[object setValue:@"Daniel" forKey:@"name"]

这个不仅可以访问作为对象属性,而且也能访问一些标量(例如 int 和 CGFloat)和 struct(例如 CGRect)。Foundation 框架会为我们自动封装它们。举例来说,如果有以下属性:

@property (nonatomic) CGFloat height;

我们可以这样设置:

[object setValue:@(20) forKey:@"height"]

键路径 (key path)

KVC 同样允许我们通过关系来访问对象。假设 person 对象有属性 address,address 有属性 city,我们可以这样通过 person 来访问 city:

[person valueForKeyPath:@"address.city"]

值得注意的是这里我们调用 -valueForKeyPath: 而不是 -valueForKey:

Key-Value Coding Without @property

不需要 @property 的 KVC

我们可以实现一个支持 KVC 而不用 @property 和 @synthesize 或是自动 synthesize 的属性。最直接的方式是添加 - 和 -set: 方法。例如我们想要 name ,我们这样做:

- (NSString *)name;
- (void)setName:(NSString *)name;

这完全等于 @property 的实现方式。

但是当标量和 struct 的值被传入 nil 的时候尤其需要注意。假设我们要 height 属性支持 KVC 我们写了以下的方法:

- (CGFloat)height;
- (void)setHeight:(CGFloat)height;

然后我们这样调用:

[object setValue:nil forKey:@"height"]

这会抛出一个 exception。要正确的处理 nil,我们要像这样 override -setNilValueForKey:

- (void)setNilValueForKey:(NSString *)key
{
    if ([key isEqualToString:@"height"]) {
        [self setValue:@0 forKey:key];
    } else
        [super setNilValueForKey:key];
}

集合的操作

一个常常被忽视的 KVC 特性是它对集合操作的支持。举个例子,我们可以这样来获得一个数组中最大的值:

NSArray *a = @[@4, @84, @2];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.self"]);

或者说,我们有一个 Transaction 对象的数组,对象有属性 amount 的话,我们可以这样获得最大的 amount:

NSArray *a = @[transaction1, transaction2, transaction3];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.amount"]);

当我们调用 [a valueForKeyPath:@"@max.amount"] 的时候,它会在数组 a 的每个元素中调用 -valueForKey:@"amount" 然后返回最大的那个。

常见的 KVO 错误

首先,KVO 兼容是 API 的一部分。如果类的所有者不保证某个属性兼容 KVO,我们就不能保证 KVO 正常工作。苹果文档里有 KVO 兼容属性的文档。例如,NSProgress 类的大多数属性都是兼容 KVO 的。

当做出改变以后,有些人试着放空的 -willChange 和 -didChange 方法来强制 KVO 的触发。KVO 通知虽然会生效,但是这样做破坏了有依赖于 NSKeyValueObservingOld 选项的观察者。详细来说,这影响了 KVO 对观察键路径 (key path) 的原生支持。KVO 在观察键路径 (key path) 时依赖于 NSKeyValueObservingOld 属性。

我们也要指出有些集合是不能被观察的。KVO 旨在观察关系 (relationship) 而不是集合。我们不能观察 NSArray,我们只能观察一个对象的属性——而这个属性有可能是 NSArray。举例说,如果我们有一个 ContactList 对象,我们可以观察它的 contacts 属性。但是我们不能向要观察对象的 -addObserver:forKeyPath:... 传入一个 NSArray。

相似地,观察 self 不是永远都生效的。而且这不是一个好的设计。

调试 KVO

你可以在 lldb 里查看一个被观察对象的所有观察信息。

(lldb) po [observedObject observationInfo]

这会打印出有关谁观察谁之类的很多信息。

这个信息的格式不是公开的,我们不能让任何东西依赖它,因为苹果随时都可以改变它。不过这是一个很强大的排错工具。

参考:

www.objccn.io/issue-7-3/