KVO使用方法
给对象的指定属性绑定监听,并设置监听类型。每当该对象指定的属性被改变时,系统就会调用observeValueForKeyPath方法。
- (void)viewDidLoad {
[super viewDidLoad];
People *people = [People new];
self.people = people;
/*
作用:给对象绑定一个监听器(观察者)
- Observer 观察者
- KeyPath 要监听的属性
- options 选项(方便在监听的方法中拿到属性值)
*/
[people addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
// 移除监听
// [people removeObserver:self forKeyPath:@"name"];
}
/**
* 当监听的属性值发生改变
*
* @param keyPath 要改变的属性
* @param object 要改变的属性所属的对象
* @param change 改变的内容
* @param context 上下文
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
NSLog(@"%@------%@------%@", keyPath, object, change);
}
实现原理
其实,每当我们使用KVO的addObserver
时,系统会创建一个派生类NSKVONotifying_People
。然后,系统动态地让其继承People类,并添加方法:setName
。在NSKVONotifying_People
的setName方法中添加observeValueForKeyPath
方法的调用。
在KVO的使用中,我们对NSKVONotifying_People
是完全不知情的。那么,我们如何调用它的setName呢?其实,在创建NSKVONotifying_People
时,我们就将People
的isa指针指向,换成了NSKVONotifying_People
。
在addObserver方法前后分别打断点查看People的isa指针,如下所示:
可以看到,添加observe之后,people的isa指针变成了NSKVONotifying_People
self.person1 = [[Person alloc] init];
self.person1.age = 1;
self.person2 = [[Person alloc] init];
self.person2.age = 2;
NSLog(@"添加监听前person1 class = %s person2 class = %s",object_getClassName(self.person1),object_getClassName(self.person2));
// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
NSLog(@"添加监听后person1 class = %s person2 class = %s",object_getClassName(self.person1),object_getClassName(self.person2));
输出如下结果:
添加监听前person1 class = Person person2 class = Person
添加监听后person1 class = NSKVONotifying_Person person2 class = Person
系统为何动态创建一个NSKVONotifying_Person类?
为了重写setAge方法,做监听赋值,在set方法中通知监听者值发生改变。
查看NSKVONotifying_Person
类的setAge:方法,发现是调用了Foundation框架里的_NSSetIntValueAndNotify
函数
IMP pp = [self.person1 methodForSelector:@selector(setAge:)];
(IMP) pp = 0x00007fff207bc2b7 (Foundation`_NSSetIntValueAndNotify)
_NSSetIntValueAndNotify
函数中做了什么?
1、willChangeValueForKey:
2、真正修改值
3、didChangeValueForKey:
(方法内部调用observer的observeValueForKeyPath:ofObject:change:context:
方法)
验证--重写Person类的部分方法
- (void)setAge:(int)age
{
_age = age;
NSLog(@"setAge");
}
- (void)willChangeValueForKey:(NSString *)key {
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey");
}
- (void)didChangeValueForKey:(NSString *)key {
NSLog(@"didChangeValueForKey begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey end");
}
输出
willChangeValueForKey
setAge
didChangeValueForKey begin
监听到<Person: 0x6000004c8350>的age属性值改变了 - {
kind = 1;
new = 21;
old = 1;
}
didChangeValueForKey end
NSKVONotifying_Person
类有哪些方法?
查看某个类的方法数组
- (void)printMethodNamesOfClass:(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);
}
查看NSKVONotifying_Person
和Person
类对象的方法列表如下:
NSKVONotifying_Person setAge:, class, dealloc, _isKVOA
Person setAge:, age
发现NSKVONotifying_Person
重写了setAge:, class, dealloc, _isKVOA
方法
当调用self.person1.age = 10
时,相当于给person1实例对象发setAge:
的消息,person1实例对象通过isa指针找到NSKVONotifying_Person
类对象的方法列表查找到setAge:
方法实现也即调用了Foundation内部的_NSSetIntValueAndNotify
函数,此函数内部通过superclass指针找到Person类对象调用原本的setAge:
方法
self.person1.age = 10;
相当于
[self.person1 setAge:10];
最终转换成runtime的伪代码:
objc_msgSend(self.person1,@selector(setAge:),10)
NSKVONotifying_Person
类为何重写class
方法?
1、隐藏NSKVONotifying_Person
类,对外界调用者无感。
手动触发监听通知?
[self.person1 willChangeValueForKey:@"age"];
sele.person1->_age = 0;
[self.person1 didChangeValueForKey:@"age"];
PS
Foundation框架中共有以下相关的函数:
_NSSetBoolValueAndNotify
_NSSetCharValueAndNotify
_NSSetDoubleValueAndNotify
_NSSetFloatValueAndNotify
_NSSetLongValueAndNotify
_NSSetLongLongValueAndNotify
_NSSetIntValueAndNotify
_NSSetObjectValueAndNotify
_NSSetPointValueAndNotify
_NSSetRangeValueAndNotify
_NSSetRectValueAndNotify
_NSSetShortValueAndNotify
_NSSetSizeValueAndNotify