欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS-解析KVO的底层实现

程序员文章站 2022-04-13 12:21:06
...

之前讲过KVO的简单应用,这次来讲解下KVO监听属性值的变化到底是怎么实现的,在监听的时候苹果在背后都做了哪些事情。

1.首先我们先来创建一个Person

@interface Person : NSObject
{
    @public
    NSString * _nickname;
}

@property (nonatomic , copy) NSString *name;

这里需要说下,这里我们用两种方式给Person创建两个属性。我们知道创建name时,会生成相应的setter/getter方法以及_name;创建 _nickname的时候是不会生成setter/getter方法,不明白的请看这里。

在创建_nickname时,如果想要在外部放这个属性需要加在@public将其公开,否则外部放不了这个成员变量,会报错(说成员变量受到了保护)!!

2.我们设置监听


static NSString * nameKey;
static NSString *nicknameKey;

Person *person = [[Person alloc] init];
_person = person;

[_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:&nameKey];

[_person addObserver:self forKeyPath:@"_nickname" options:NSKeyValueObservingOptionNew context:&nicknameKey];

这里我们对person的两个成员变量name 和 _nickname进行监听,我们来比较下,看看这两种方法创建的属性是不是都能被监听到?

实现:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    if (context == &nameKey) {
        NSLog(@"name改变了");
    }else if (context == &nicknameKey){
        NSLog(@"nickname改变了");
    }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    static int i = 0;
    i ++ ;

    _person.name = [NSString stringWithFormat:@"name = %d",i];
    _person->_nickname = [NSString stringWithFormat:@"nickname = %d",i];

    NSLog(@"%@   %@",_person.name,_person->_nickname);    
}

当我们touchesBeganw的时候你发现name值的改变被监听到了,而_nickname值的改变没有被监听到,这还是什么原因呢???

这里就要说KVO的到底是怎么实现的了

KVO实现原理

我们先打个断点在这里,调试看看,这里面有什么猫腻

iOS-解析KVO的底层实现

这个时候看你的控制台:

iOS-解析KVO的底层实现

我们看到这时候的person类型还是Person,这个isa指针指向的是真实数据类型。然后我们向下走一步会看到:

iOS-解析KVO的底层实现

咦!person的数据类型变成 NSKVONotifying_Person 了,这是什么情况??难道苹果默默的做了些不为人知的是事情??答案是肯定的。

首先当我们在添加监听的时候,苹果会在内存中默默的为我们创建一个类名字就叫做 NSKVONotifying_XXX ,在这个类里面,会去重写name的setter方法

- (void)setName:(NSString *)name
{
    [self willChangeValueForKey:@"name"];// 老值
    [super setName:name];
    [self didChangeValueForKey:@"name"]; // 新值
}

在setter方法中会执行这个操作,所以我们能够通过 observeValueForKeyPath: 方法收到新值和老值。

回到之前的问题,_nickname属性值变化不能够被监听的原因也就出来了,因为 _nickname没有setter/getter方法,也就没发重写setter方法,最终导致无法监听其值的变化。

以上是我对KVO实现原理的理解,有不对的地方可以评论处回复,我们共同研究!

Demo下载地址。

相关标签: KVO