iOS底层第四天--KVO

作者: 三月木头 | 来源:发表于2020-04-01 17:00 被阅读0次

三个问题作为引导:

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

派生类内部伪代码.png

既然KVO的时候新生成了派生类,那这个派生类的类对象中isa指向哪里?

派生的类对象的isa指向派生元类对象。


1. KVO本质是什么?

1)利用 runtime的API动态生成派生的子类,并且让instance对象的isa指向这个 全新的子类。
2)当修改instance对象属性时,调用Foundation的_NSSetXXXValueAndNotify函数。这个函数内部伪代码
willchangevalueForKey:
[super sette:];
didchangeValueForKey:(这个里面触发监听器的监听方法)

下图说明监听本质:


KVO监听对象原理.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);
}

相关文章

网友评论

    本文标题:iOS底层第四天--KVO

    本文链接:https://www.haomeiwen.com/subject/onbouhtx.html