KVO 的基本和高级使用

  1. context 如果不需要使用,填 NULL。如果需要使用 context 推荐传递静态变量。官方文档中还提到,在父类和子类中同时监听某个属性时无法使用 keyPath 区分的情况下就可以使用 context 进行区分。
  2. 是否有必要移除观察者?当然有必要,该移除的时候就一定要移除
  3. automaticallyNotifiesObserversForKey: 的使用
  4. keyPathsForValuesAffectingValueForKey: 的使用
  5. 可变数组属性的 KVO 问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* 用于与 `-addObserver:forKeyPath:options:context:` 和 `-addObserver:toObjectsAtIndexes:forKeyPath:options:context:` 一起使用的选项。
*/
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {

/* 通知中发送的变更字典是否应分别包含 `NSKeyValueChangeNewKey` 和 `NSKeyValueChangeOldKey` 条目。
*/
NSKeyValueObservingOptionNew = 0x01,
NSKeyValueObservingOptionOld = 0x02,

/* 是否应在观察者注册方法返回之前立即向观察者发送通知。
如果同时指定了 `NSKeyValueObservingOptionNew`,则通知中的变更字典将始终包含 `NSKeyValueChangeNewKey` 条目,但永远不会包含 `NSKeyValueChangeOldKey` 条目。(在初始通知中,被观察属性的当前值可能是旧的,但对观察者来说是新的。)
你可以使用此选项,而不是在同一时间显式调用观察者的 `-observeValueForKeyPath:ofObject:change:context:` 方法也会调用的代码。
当此选项与 `-addObserver:toObjectsAtIndexes:forKeyPath:options:context:` 一起使用时,将为每个被添加观察者的索引对象发送通知。
*/
NSKeyValueObservingOptionInitial API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x04,

/* 是否应在每次变更前后分别向观察者发送通知,而不是在变更后发送单一通知。
在变更前发送的通知中的变更字典始终包含一个 `NSKeyValueChangeNotificationIsPriorKey` 条目,其值为 `[NSNumber numberWithBool:YES]`,但永远不会包含 `NSKeyValueChangeNewKey` 条目。
当观察者自身的 KVO 合规性要求其为其自身属性之一调用 `-willChange...` 方法,并且该属性的值取决于被观察对象属性的值时,可以使用此选项。(在这种情况下,在接收到 `-observeValueForKeyPath:ofObject:change:context:` 消息后,已经太晚,无法轻松地正确调用 `-willChange...`。)

当指定此选项时,变更后发送的通知中的变更字典将包含与未指定此选项时相同的条目,除了由 `NSOrderedSet` 表示的有序唯一对多关系。
对于这些关系,对于 `NSKeyValueChangeInsertion` 和 `NSKeyValueChangeReplacement` 变更,will-change 通知的变更字典包含一个 `NSKeyValueChangeIndexesKey`(以及在 `Replacement` 情况下,如果注册时指定了 `NSKeyValueObservingOptionOld` 选项,则还包含 `NSKeyValueChangeOldKey`),这些条目给出了可能被操作更改的索引(和对象)。
第二个通知,即变更后的通知,包含报告实际发生变更的条目。
对于 `NSKeyValueChangeRemoval` 变更,按索引的删除是精确的。
*/
NSKeyValueObservingOptionPrior API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x08

};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@interface NSObject(NSKeyValueObserving)

/* 假设接收者已经注册为相对于某个对象的键路径的值的观察者,当该值发生变化时,接收者会收到通知。

变更字典始终包含一个 `NSKeyValueChangeKindKey` 条目,其值是一个包装了 `NSKeyValueChange` 的 `NSNumber`(使用 `-[NSNumber unsignedIntegerValue]` 获取)。`NSKeyValueChange` 的含义取决于键路径所标识的属性的类型:
- 对于任何类型的属性(属性、一对一关系、有序或无序的多对多关系),`NSKeyValueChangeSetting` 表示被观察对象收到了 `-setValue:forKey:` 消息,或者调用了符合键值编码的 set 方法,或者以其他方式调用了 `-willChangeValueForKey:/-didChangeValueForKey:` 对。
- 对于**有序**的多对多关系,`NSKeyValueChangeInsertion`、`NSKeyValueChangeRemoval` 和 `NSKeyValueChangeReplacement` 表示向对象发送的 `-mutableArrayValueForKey:` 消息返回的数组,或向对象发送的 `-mutableOrderedSetValueForKey:` 消息返回的有序集合,被发送了修改消息,或者调用了符合键值编码的数组或有序集合的修改方法,或者以其他方式调用了 `-willChange:valuesAtIndexes:forKey:/-didChange:valuesAtIndexes:forKey:` 对。
- 对于**无序**的多对多关系(在 Mac OS 10.4 中引入),`NSKeyValueChangeInsertion`、`NSKeyValueChangeRemoval` 和 `NSKeyValueChangeReplacement` 表示向对象发送的 `-mutableSetValueForKey:` 消息返回的集合被发送了修改消息,或者调用了符合键值编码的集合的修改方法,或者以其他方式调用了 `-willChangeValueForKey:withSetMutation:usingObjects:/-didChangeValueForKey:withSetMutation:usingObjects:` 对。

对于任何类型的属性,如果在观察者注册时指定了 `NSKeyValueObservingOptionNew`,并且是正确类型的变更,且这不是一个“变更前”通知,则变更字典包含一个 `NSKeyValueChangeNewKey` 条目。如果在观察者注册时指定了 `NSKeyValueObservingOptionOld`,并且是正确类型的变更,则变更字典包含一个 `NSKeyValueChangeOldKey` 条目。关于这些条目的值可能是什么,请参阅 `NSKeyValueObserverNotification` 非正式协议方法的注释。

对于**有序**的多对多关系,除非变更是 `NSKeyValueChangeSetting`,否则变更字典始终包含一个 `NSKeyValueChangeIndexesKey` 条目,其值是一个包含插入、删除或替换对象的索引的 `NSIndexSet`。

如果在观察者注册时指定了 `NSKeyValueObservingOptionPrior`(在 Mac OS 10.5 中引入),并且此通知是由于变更前发送的通知,则变更字典包含一个 `NSKeyValueChangeNotificationIsPriorKey` 条目,其值是一个包装了 `YES` 的 `NSNumber`(使用 `-[NSNumber boolValue]` 获取)。

`context` 始终与观察者注册时传入的指针相同。
*/
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;

@end

KVO 的底层实现原理

isa-swizzling

自己实现一个简单的 KVO

查看 GNU 的 KVO 实现或逆向分析 Foundation 源码