KVO简介
KVO全称为(Key-Value-Observing),俗称兼职兼听,用于监听摸个对象属性值的改变,由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。
常用的操作方法有:
//注册指定路径的监听器: 观察者可以接收keyPath属性的变化事件。
addObserver: forKeyPath: 属性名 options : context:nil
//监听回调:当keyPath属性发生改变后,KVO会回调这个方法来通知观察者。
observeValueForKeyPath: ofObject: change: context:
//删除指定key路径的监听
//会在dealloc移除监听,需要注意的是,调用removeObserver需要在观察者消失之前,否则会导致Crash。
removeObserver: forKeyPath、removeObserver: forKeyPath: context:
未被KVO监听的对象,改变时会直接调用set,get方法
未被KVO监听的对象.png
添加了KVO的监听属性改变,isa指针会只想runtime生成的一个派生类NSKVONotifying_Person,NSKVONotifying_person是Person类的子类,会调用Foundation框架里面的_NSSetIntValueAndNotify
添加了KVO监听的对象.png
1、KVO的使用
在Person
类中有一个age
属性.
@interface Person : NSObject
@property (nonatomic ,assign) int age;
@end
在Controller
中监听Person
对象age
属性的改变, 实现observeValueForKeyPath
方法.
- (void)viewDidLoad {
[super viewDidLoad];
Person *p1 = [[Person alloc] init];
[p1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
p1.age = 10;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性改变:%@", object, keyPath, change);
}
-(void)dealloc{
[Person removeObserver:self forKeyPath:@"age"];
}
当对p1的age属性赋值时, 会调用observeValueForKeyPath方法, 监听到age属性的改变.
2、KVO的本质
- (void)viewDidLoad {
[super viewDidLoad];
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
[p1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
p1.age = 10;
p2.age = 10;
NSLog(@"%@--%@", p1, p2);
}
我们来研究一下, 为p1添加监听以后, p1的isa指针是否发生了变化?
isa变化.png
1,可以发现, 为p1添加监听以后
p1
的isa指针
指向了一个全新的类NSKVONotifying_Person
,p2
的isa指针
还是指向Person
.
2,注意这里, 如果使用[p1 class]获取p1的类型是不准确的, 后续会有解释
p1
的isa指针
发生了改变, 当调用p
对象的age属性赋值时, 其本质是调用的哪个方法呢?
- (void)viewDidLoad {
[super viewDidLoad];
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
NSLog(@"p1添加KVO监听之前 - %p %p",
[p1 methodForSelector:@selector(setAge:)],
[p2 methodForSelector:@selector(setAge:)]);
[p1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
NSLog(@"p1添加KVO监听之后 - %p %p",
[p1 methodForSelector:@selector(setAge:)],
[p2 methodForSelector:@selector(setAge:)]);
p1.age = 10;
p2.age = 10;
NSLog(@"%@--%@", p1, p2);
}
方法调用.png
通过对比查看为
p1添加监听以后
调用的方法, p1
添加监听之前调用的setAge
方法, 而添加监听以后
, 调用的是Foundation
中的_NSSetIntValueAndNotify方法
.
NSKVONotifying_Person
是Person
的一个子类, 在NSKVONotifying_Person
内部也有setAge
方法.
添加监听以后, 对象的isa指针
发生变化. isa指向的类对象发生变化, 生成一个全新的类
, 继承自原来的类.
-set方法发生变化, 调用一个C语言的私有函数, 在私有函数中会调用 willChangeValueForKey
, set方法, didChangeValueForKey
.
3、查看中间类的内容.
@implementation NSKVONotifying_Person1
-(void)setAge:(int)age{
_NSSetIntValueAndNotify();
}
void _NSSetIntValueAndNotify(){
[self willChangeValueForKey:age];
[super setAge:age];
[self didChangeValueForKey:age];
}
-(void)didChangeValueForKey:(NSString *)key{
[observer observeValueForKeyPath:@"age" ofObject:self change:@{} context:nil];
}
- (Class)class
{
return class_getSuperclass(object_getClass(self));
}
@end
如何手动触发KVO?
,通过中间类的实现可以知道,必须手动调用willChange和didChange方法
,然后才会触发监听的回调
网友评论