iOS | KVO | Objective-C
KVO的本质是什么,如何手动触发KVO?
1.什么是KVO
KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变
添加监听:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20typedef enum NSKeyValueObservingOptions : NSUInteger {
// 新值(包含于回调方法change字典中)
NSKeyValueObservingOptionNew = 0x01,
// 旧值(包含于回调方法change字典中)
NSKeyValueObservingOptionOld = 0x02,
// 观察最初的值(在注册观察服务时会调用一次触发方法)
NSKeyValueObservingOptionInitial = 0x04,
// 分别在值修改前后触发方法(即一次修改有两次触发)
NSKeyValueObservingOptionPrior = 0x08
} NSKeyValueObservingOptions;
/**
监听属性方法,方法调用者为被观察对象
@param observer 观察者/订阅者
@param keyPath 要观察的属性
@param options 监听变化条件
@param context 上下文,将会传递到监听回调函数中
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
监听回调:1
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
移除监听:1
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
KVO使用大家都比较熟悉,Demo应该就没有写的必要了,下面我们直接来探索下本质
2.KVO的本质
1 | - (void)viewDidLoad { |
通过断点调试,我们发现 person2 的类对象没有发生变化,person1 的类对象变成了 NSKVONotifying_Person,而且是Person的子类。
使用 Runtime 打印 NSKVONotifying_Person 方法列表:1
2
3
4
5
6
7
8
9
10
11
12
13- (void)printMethodNamesOfClass:(Class)cls {
unsigned int methCount = 0;
NSMutableArray *methodArr = [NSMutableArray array];
Method *meths = class_copyMethodList(cls, &methCount);
for(int i = 0; i < methCount; i++) {
Method meth = meths[i];
SEL sel = method_getName(meth);
NSString *methodName = NSStringFromSelector(sel);
[methodArr addObject:methodName];
}
if (methodArr.count) NSLog(@"%@", methodArr);
free(meths);
}
- NSKVONotifying_Person 实例方法列表:
- setAge:
- class
- dealloc
- _isKVOA
总结:
1> 添加监听时,利用RuntimeAPI动态生成Person子类: NSKVONotifying_Person ,并且使 person1 的 isa 指针指向新的类
2> 重写 setAge: ,person1 调用 setter 方法时会从 NSKVONotifying_Person 开始查找,在自己的类对象中能够找到,所以会调用自己的 setAge:方法( 会调用Foundation的_NSSetValueAndNotify函数)
3> _NSSetValueAndNotify 调用流程:willChangeValueForKey -> [super setAge:] (Person 的 setter 方法) -> didChangeValueForKey(同时触发 observeValueForKeyPath 监听回调方法,订阅者接收)
4> class 方法:重写 class 方法的目的是什么呢?(lldb) po self.person1.class 输出为:Person,原来,苹果粑粑是想要隐藏NSKVONotifying_Person,让开发者无感,使用时与未添加监听时无异
5> dealloc方法:释放 KVO 新产生的资源
6> _isKVOA方法:标记这个新类 KVO 机制新建的
对willChangeValueForKey/didChangeValueForKey还有疑惑的同学可以在 Person.m 中对这两个方法进行重写,再进行调试以变理解。