三个问题作为引导:
1. KVO本质是什么?
2. 如何手动调用KVO?
3. 直接修改成员变量会触发KVO吗?
KVO是什么:KVO 是Key-Value-Observer的简称,用来观察对象某个属性变化。
如何用KVO?
//监听属性变化前后值
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
注意不用监听后一定删除,防止内存泄漏
[self.person1 removeObserver:self forKeyPath:@"age"];
我们想知道,对象相关被添加监听后相关属性变化为什么就能被监听?
思路:
为此我们可以对同一个类实例两个对象,其中一个添加监听,另外一个不被监听。然后改变属性值的本质其实就是调用set方法,通过set方法赋值,一个类只有一个类对象,所以方法地址理论上是唯一的。所以我们可以打印一下相关set方法的指针前后是否有变化。[self.person1 methodForSelector:@selector(setAge:)], [self.person2 methodForSelector:@selector(setAge:)]);
通过对比前后地址发现方法名地址不同,则说明setAge 方法所属的类对象变了,我们可以通过lldb模式下打印指针所在项目位置进行核实p (IMP)0x1000xx1d
进行查看。如下图打印指针内所在项目位置.png 如果再进一步核实,我们可以再看一下实例对象的isa是不是一样的就能核对出问题了。通过打印我们会发现,添加KVO后的isa指向的不再是原来的类对象,而是一个衍生出的继承自原类的类NSKVONotifying_MJPerson.h。
综上所诉,我们添加KVO后,实例对象的isa指向了派生类,执行的派生类的set方法,派生类set方法内部实现的是foundation 框架下的
_NSSetIntValuseAndNotify的方法。我们可以猜测一下这个派生类内部伪代码如下:
- (void)setAge:(int)age
{
_NSSetIntValueAndNotify();
}
// 伪代码void _NSSetIntValueAndNotify()
{
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key
{
// 通知监听器,某某属性值发生了改变
[oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
@end
![](https://img.haomeiwen.com/i20051074/3ec880c8a9965072.png)
既然KVO的时候新生成了派生类,那这个派生类的类对象中isa指向哪里?
派生的类对象的isa指向派生元类对象。
1. KVO本质是什么?
1)利用 runtime的API动态生成派生的子类,并且让instance对象的isa指向这个 全新的子类。
2)当修改instance对象属性时,调用Foundation的_NSSetXXXValueAndNotify函数。这个函数内部伪代码
willchangevalueForKey:
[super sette:];
didchangeValueForKey:(这个里面触发监听器的监听方法)
下图说明监听本质:
![](https://img.haomeiwen.com/i20051074/2e5d0c83ad9882b4.png)
2. 如何手动调用KVO?
手动调用
1)willchangevalueForKey:
2)didchangeValueForKey:
必须二者都存在才行,因为didchangeValueForKey实现里面判断是否实现了第一步willchangevalueForKey。如果第一步没有实现,那么第二步就不会执行下去。
3. 直接修改成员变量会触发KVO吗?
answer:不会触发,因为没有走触发set方法。
举例说明一下:
self.person.age = 1; 会触发set所有此方法会触发KVO
self.person -> _age = 2; 是直接修改成员属性,不涉及set方法
打印一个类中的所有方法:
- (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:@", "];
}
// C语音 中 copy 了内存就要释放
free(methodList);
NSLog(@"%@ %@", cls, methodNames);
}
网友评论