一、是个啥
KVO全称为Key Value Observing,键值监听,可以用于监听某个对象属性值的改变。
是观察者设计模式的一种实现。
二、用法
简单试用
#pragma mark - KVO
-(void)KVOTest{
self.person = [[Person alloc] init];
self.person.age = 10;
//给person对象添加监听对象
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"123"];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.person.age = 20;
}
//响应方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
//观察者观察name的变化,当点击屏幕,改变name的值,chang就会捕获新值
NSLog(@"%@-%@-%@-%@",keyPath,object,change,context);
}
//移除监听
-(void)dealloc{
[self.person removeObserver:self forKeyPath:@"age"];
}
三、KVO的底层是怎么实现的?
self.xiaoming1 = [[XiaoMing alloc] init];
self.xiaoming1.age = 1;
self.xiaoming2 = [[XiaoMing alloc] init];
self.xiaoming2.age = 2;
[self.xiaoming1 addObserver:self //本控制器来监听
forKeyPath:@"age"//监听xiaoming1 age 的变化
options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld//通常选这2个,下面会解释
context:@""];//下面会演示
1、疑问
走的都是XiaoMing类方法setAge,为什么xiaoming1的age值,VC能监听到值的改变,xiaoming2不能?
这不是setAge的问题,是对象的问题
我们来打印一下这2个对象的isa,发现不一样
image.png
2、本质分析
未使用kvo监听的对象
请忽略图中的命名
使用kvo监听的对象
请忽略图中的命名.png
NSKVONotifying_XiaoMing是使用runtime动态创建的一个类,这就是OC强大的地方,可以在运行的过程中,自己创建一个类。
NSKVONotifying_XiaoMing是XiaoMing的子类。
3、本质验证
打印一下xiaoming1 、xiaoming2的类对象
NSLog(@"🍎监听之前--xiaoming1的类对象:%@-xiaoming2的类对象:%@",object_getClass(self.xiaoming1),
object_getClass(self.xiaoming2));
[self.xiaoming1 addObserver:self //本控制器来监听
forKeyPath:@"age"//监听xiaoming1 age 的变化
options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld//通常选这2个,下面会解释
context:@""];//下面会演示
NSLog(@"🍎监听之后--xiaoming1的类对象:%@-xiaoming2的类对象:%@",object_getClass(self.xiaoming1),
object_getClass(self.xiaoming2));
image.png
打印一下xiaoming1 、xiaoming2的setAge方法实现
NSLog(@"🍎监听之前--方法实现:%p-%p",[self.xiaoming1 methodForSelector:@selector(setAge:)],
[self.xiaoming2 methodForSelector:@selector(setAge:)]);
[self.xiaoming1 addObserver:self //本控制器来监听
forKeyPath:@"age"//监听xiaoming1 age 的变化
options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld//通常选这2个,下面会解释
context:@""];//下面会演示
NSLog(@"🍎监听之后--方法实现:%p-%p",[self.xiaoming1 methodForSelector:@selector(setAge:)],
[self.xiaoming2 methodForSelector:@selector(setAge:)]);
image.png
再来看一下方法实现
image.png
打印一下类对象和元类对象
NSLog(@"🍎xiaoming1:类对象-%@,元类对象-%@",object_getClass(self.xiaoming1),object_getClass(object_getClass(self.xiaoming1)));
NSLog(@"🍎xiaoming2:类对象-%@,元类对象-%@",object_getClass(self.xiaoming2),object_getClass(object_getClass(self.xiaoming2)));
image.png
4、Foundation里的_NSSetValueAndNotify的实现
如果会点逆向的知识可能会好探索一些,这里直接说结论
打印调用顺序
-(void)setAge:(int)age{
_age = age;
NSLog(@"setAge");
}
-(void)willChangeValueForKey:(NSString *)key{
[super willChangeValueForKey:key];//为了干扰以前的实现,调用super
NSLog(@"willChangeValueForKey");
}
-(void)didChangeValueForKey:(NSString *)key{
NSLog(@"didChangeValueForKey-开始");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey-结束");
}
image.png
四、问题
1、iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
1、使用runtime API动态生产一个子类,并且让实例对象的isa指向这个全新的
子类
2、当修改对象的属性时,会调用Foundation里的_NSSetXXXXAndNotify的函数
- willChangeValueForKey
- 父类原来的setter
- didChangeValueForKey
- 内部会触发监听器observe的监听方法:observeValueForKeyPath
2、如何手动触发KVO?
这个题的理解应该是不改变对象的属性值,也能触发KVO
手动调用willChangeValueForKey和didChangeValueForKey方法。
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.xiaoming1 willChangeValueForKey:@"age"];
[self.xiaoming1 didChangeValueForKey:@"age"];
}
image.png
3、直接修改成员变量会不会触发KVO?
不会
4、KVO的运用场景?
网友评论