欢迎大家关注我的公众号,我会定期分享一些我在项目中遇到问题的解决办法和一些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]
这会打印出有关谁观察谁之类的很多信息。
这个信息的格式不是公开的,我们不能让任何东西依赖它,因为苹果随时都可以改变它。不过这是一个很强大的排错工具。
参考: