「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」
前言
上一篇文章,我们分析过了KVC原理,那这次我们就来分析一下KVO。对于KVO,我们并不陌生,项目中很经常用到,大家也知道怎么用,那我们知道它的原理实现是怎样的呢?今天我们就一起来探索一下。
KVO
KVO定义
KVO的全称是(Key-Value Observing),俗称“键值监听",可以用于监听某个对象属性值的改变。
简单来说KVO可以通过监听key,来获得value的变化,用来在对象之间监听状态变化。
KVO使用
- 添加监听对象属性
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
这里的name就是self.person的类的属性,options传的是内容变化类型。
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
// 通知改变后的值
NSKeyValueObservingOptionNew = 0x01,
// 通知改变前的值
NSKeyValueObservingOptionOld = 0x02,
NSKeyValueObservingOptionInitial = 0x04,
NSKeyValueObservingOptionPrior = 0x08
}
- 对象属性改变通知
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"%@",[change objectForKey:@"old"]);
NSLog(@"%@",[change objectForKey:@"new"]);
}
}
这里的keyPath就是属性name,change会返回旧的值和新的值。当然我们可以根据context进行区分,那上面就不能传NULL。
- 手动释放监听
- (void)dealloc{
// [self.person removeObserver:self forKeyPath:@"name"];
}
我们会在dealloc里面调用removeObserver来进行释放。
手动监听
上面的都是自动实现监听,如果我们想手动监听,又要如何做呢?
- 首先我们要把想要监听的属性设置为不自动监听。我们把
automaticallyNotifiesObserversForKey方法里的key为name时设置为NO。
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:@"name"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- 在我们的
setName方法里面添加willChangeValueForKey和didChangeValueForKey。
- (void)setName:(NSString *)name
{
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
这样就可以实现手动监听,其实在自动监听里面,系统调用set方法也会调用willChangeValueForKey和didChangeValueForKey来通知observeValueForKeyPath回调。
监听可变数组
监听可变数组,这里的写法和之前的有点不一样。
[self.person addObserver:self forKeyPath:@"dataArray" options:(NSKeyValueObservingOptionNew) context:NULL];
dataArray是个可变数组,可以在模型init的时候进行初始化。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"dataArray"]) {
NSLog(@"%@",[change objectForKey:@"new"]);
}
}
observeValueForKeyPath里面的写法也是不变的,主要是数组添加成员的方式要改变一下。要用mutableArrayValueForKey来添加。
[[self.person mutableArrayValueForKey:@"dataArray"] addObject:@"1"];
我们来打印下结果:
(lldb) po change
{
indexes = "<_NSCachedIndexSet: 0x6000015dcb00>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
kind = 2;
new = (
1
);
}
那这里的kind是1个枚举。
typedef NS_ENUM(NSUInteger, NSKeyValueSetMutationKind) {
// 赋值
NSKeyValueUnionSetMutation = 1,
// 插入
NSKeyValueMinusSetMutation = 2,
// 移除
NSKeyValueIntersectSetMutation = 3,
// 替换
NSKeyValueSetSetMutation = 4
};
KVO原理
KVO是通过isa-swizzling来实现的,在addObserver的时候回利用Runtime API动态生成一个子类,并且让instance对象的isa指向这个全新的子类。
我们从图片可以看出,在addObserver后,self.person的类就变成了JJPerson的子类NSKVONotifying_JJPerson。
NSKVONotifying_JJPerson里面有什么东西呢
isa和superclass
类都是有isa和superclass。这里的superclass就是我们的父类JJPerson。
set方法
NSKVONotifying_JJPerson里面会重写父类的set方法,例如setName:
- (void)setName:(NSString *)name
{
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
class
NSKVONotifying_JJPerson里面有class方法,这里的class指向的就是我们的类JJPerson。如果不看原理,我们完全是不知道我们的对象的isa指向已经发生变化了。
class源码如下:
- (Class) class
{
return class_getSuperclass(object_getClass(self));
}
我们打印一下:
(lldb) po [self.person class]
JJPerson
dealloc
NSKVONotifying_JJPerson也重新了父类的dealloc方法来释放资源,而且调用removeObserver后,person的isa指针就会重新指向JJPerson。
而且KVO销毁后,KVO销毁,NSKVONotifying_JJPerson并没有从内存中移除,等下次注册的时候,就会重新添加回来。
_isKVOA
NSKVONotifying_JJPerson也会重写_isKVOA,这个是判断当前类是否是KVO。
总结
我们总体上写了KVO如何去使用,也大概写了KVO的一些原理,最后附上MJ大神的截图。
参考资料