个人搭建的博客,欢迎访问:
https://www.pphtc.com/work.html
概述
KVO
是Key-Value Observing的缩写,意思是键值监听。
开发过程中,用来监听某一对象的某个属性的变化。KVO
是观察者模式的一种实现。
基本使用
YCPerson
类,有个属性为age
@interface YCPerson : NSObject
@property (nonatomic, assign) NSUInteger age;
@end
监听self.person
此对象的age
属性
[self.person addObserver:self forKeyPath:@"age" options:
(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)
context:nil];
当self.person
的age
属性被重新赋值时,会调用监听方法。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@发生了改变",keyPath);
}
当不需要监听的时候,记得在适当的地方移除监听
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"age"];
}
原理探究
打断点,看看self.person
实例对象所指向的类对象:
po self.person->isa
在self.person
调用addObserver
方法之前,输出为:
YCPerson
在self.person
调用addObserver
方法之后,输出为:
NSKVONotifying_YCPerson
小结论
当给一个对象a
加入监听代码时,runtime
会动态创建了一个类。(设示例对象a
对应的类是A
)
该类类名为
NSKVONotifying_A
该类
NSKVONotifying_A
是A
的子类
没有使用KVO时
以上面的
YCPerson
为例,调用self.person.age = 1
,发生了什么事情?
给age赋值,实际上是通过实例对象self.person
的isa
,找到其所对应的类对象YCPerson
,再调用类对象YCPerson
的方法setAge:
,完成赋值。
使用KVO时
以上面的
YCPerson
为例,调用self.person.age = 1
,发生了什么事情?
给age赋值,实际上是通过实例对象self.person
的isa
,找到其所对应的类对象NSKVONotifying_YCPerson
,(注意,这是runtime动态创建的一个类,不是原来的类对象YCPerson
),再调用类对象NSKVONotifying_YCPerson
的方法setAge:
,这个方法里不但完成了赋值,还完成了监听。下面看看这个方法里具体代码。
NSKVONotifying_YCPerson
的方法setAge:
,实际上是调用了Foundation
框架下的_NSSetUnsignedLongLongValueAndNotify
。(UnsignedLongLong
是由age的类型NSUinteger
决定的)
拓展:为啥知道是
_NSSetUnsignedLongLongValueAndNotify
呢?1.首先找到添加
KVO
后的setAge:
方法
[self.person methodForSelector:@selector(setAge:)]
获取到内存地址为
0x7fff258f1707
2.控制台打印该地址的
IMP
p (IMP)0x7fff258f1707
输出结果如下
(IMP) $1 = 0x00007fff258f1707 (Foundation
_NSSetUnsignedLongLongValueAndNotify)`
_NSSetUnsignedLongLongValueAndNotify
执行了如下代码
[self willChangeValueForKey:@"age"];
[super setAge:age]; // 完成赋值
[self didChangeValueForKey:@"age"];
didChangeValueForKey:
方法里做了监听
- (void)didChangeValueForKey:(NSString *)key {
// 通知监听器,key发生了改变
[self observeValueForKeyPath:key ofObject:self change:nil context:nil]; // 伪码:参数暂时设为nil
}
原理进一步探究
先做个小总结:
- 当给一个对象
a
加入监听代码时,runtime
会动态创建了一个类NSKVONotifying_A
,该类是A
的子类。 - 该子类
NSKVONotifying_A
,重写了set
方法,这个方法里完成了赋值和监听。
自然而然地,我们会想到动态创建的类NSKVONotifying_A
,只是重写了set
方法吗?有没有做其他事情?
于是通过runtime
动态获取该类的所有方法
/// 打印类对象的所有方法
/// @param cls 类对象
- (void)printMethodNameOfClass:(Class)cls {
unsigned int count;
// 获得方法数组
Method *methodList = class_copyMethodList(cls, &count);
// 存储方法名
NSMutableString *methodNames = [NSMutableString string];
// 遍历所有的方法
for (int i = 0; i < count; i++) {
// 获得方法
Method method = methodList[i];
// 获得方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
// 释放
free(methodList);
// 打印方法名
NSLog(@"%@所有方法 %@", cls, methodNames);
}
在分别调用KVO
前后分别调用
[self printMethodNameOfClass:object_getClass(self.person)];
打印结果为:
KVO之前
YCPerson所有方法 age, setAge:
KVO之前
NSKVONotifying_YCPerson所有方法 setAge:, class, dealloc, _isKVOA
发现动态类里有四个方法setAge:
, class
, dealloc
, _isKVOA
setAge:
上面已经详细说了,做了赋值和监听
class
加了
KVO
后,调用[YCPerson class]
会返回NSKVONotifying_YCPerson
。
所以重写class
方法,使其返回YCPerson
dealloc
推测是做了内存管理相关处理,毕竟是动态生成的一个类,释放时会处理些其它东西。
_isKVOA
添加
KVO
的标识
demo:https://github.com/1552612373/KVO_Demo
个人搭建的博客:https://www.pphtc.com/work.html