KVO
KVO 的全称是 Key-Value Observing,也叫做 “键值监听”,用于监听对象属性值的变化。
KVO 的使用
// 1.定义监听选项
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld;
// 2.添加监听
[obj addObserver:observer forKeyPath:@"keyPath" options:options context:ctx];
// 3.在 observer 类中实现监听方法
// keyPath:被改变的属性的 keyPath
// object:被改变属性的对象
// change:被改变的属性的信息
// context:上下文
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context {
// do someting...
// 从 change 中取值用到的 key
// NSKeyValueChangeNewKey:从 change 中取新值
// NSKeyValueChangeOldKey:从 change 中取旧值
}
// 4.在不需要监听时要移除监听器
[obj removeObserver:observer forKeyPath:@"keyPath" context: ctx];
KVO 的原理
- Runtime 会为被监听属性的类生成一个子类(NSKVONotifying_Xxxx),重写被监听属性的 Setter。
- 实例对象的 isa 指向这个子类。
- 子类 Setter 的内部调用 Foundation 的 _NSSetXxxValueAndNotify():
_NSSetXxxValueAndNotify() 函数内部工作流程:
- [self willChangeValueForKey:@"key"]
- [super setXxx:newValue]
- [self didChangeValueForKey:@"key"]
在 didChangeValueForKey: 方法内通知监听器:
[observer observeValueForKeyPath:key ofObject:self change:change context:ctx]
KVO 补充
在 Runtime 生成的子类中,除了重写被监听属性的 Setter,还重写了其他几个方法:
- -(Class)class:这里返回自定义类的 Class 对象,因为子类在应用层是被屏蔽的。
- -(void)dealloc:释放一些资源。
- -(Bool)_isKVOA:是否为一个 KVO 机制的类。
通过 class_copyMethodList(Class class, unsigned int *outCount) 函数可以查看一个类中的方法信息。
示例:
unsigned int count; Class class = object_getClass(obj); Method *methods = class_copyMethodList(class, &count); for (int i = 0; i < count; i++) { Method method = methods[i]; SEL selector = method_getName(method); NSString *name = NSStringFromSelector(selector); NSLog(@"%@", name); } free(methods);
如何手动触发 KVO ?
调用 willChangeValueForKey 和 didChangeValueForKey。
直接修改成员变量的值会触发 KOV 吗?
不会,因为不会调用 Setter。
如果在添加监听器时指定的是 “属性中的属性”,会有什么不同?
示例:[obj addObserver:observer forKeyPath:@"a.b" options:options context:ctx]。
Runtime 会对 obj 和 obj.a 的类生成子类,重写 obj 的 setA: 和 obj.a 的 setB:,当修改 obj.a 或 obj.a.b 时,都会通知监听器,监听器收到的 keyPath 都是 "a.b"。
开发中要避免这种用法,因为监听器不好区分修改的是哪一级的属性值。
网友评论