iOS KVO实现原理

关于KVO的实现,文章已经很多了,这里阐述我个人的观点,写一些自己的感受

1、简介

    KVO(key-value observe)是在KVC的基础上实现的一种用于监听属性变化的设计模式;如果对某个类的某个属性设置了KVO,那么当这个属性发生变化时,就会触发监听方法,从而知道属性变化了。如果本类一个属性的改变会影响到其他多个属性的变化,我们也会经常自己重写这个属性的set方法,用来监听他的变化,但是如果不是本类的属性,我们就没办法重写其set方法了,这个时候KVO就可以上场了,其实KVO本质上也是重写set方法,只不过多了一点“黑魔法”而已。

2、使用

1)设置监听

// 初始化一个测试类,类里面声明一个属性:nameStr
_kvoTest = [[KVOTestModel alloc] init];
// 给属性nameStr设置监听,类型不同,回调值不同,这里监听的是新值(NSKeyValueObservingOptionNew),还可以监听其他变化,具体看枚举
[_kvoTest addObserver:self forKeyPath:@"nameStr" options:NSKeyValueObservingOptionNew context:nil];

2)实现监听回调方法

// 监听的属性通过kvc发生变化时,执行下面方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    // 这里我们只判断了keyPath,如果存在多个kvo,为防止重名,可判断object是否为我们监听的实例:_kvoTest
    if ([keyPath isEqualToString:@"nameStr"]) {
        // 我们直接打印新值
        NSLog(@"change: %@",[change objectForKey:NSKeyValueChangeNewKey]);
    }
}

3)使用KVO时应该特别注意移除观察者,否在当类要被释放时会发生崩溃(Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance *** of class *** was deallocated while key value observers were still registered with it.)

- (void)dealloc {
    [_kvoTest removeObserver:self forKeyPath:@"nameStr"];
}

这里有一点,提一下,iOS11如果不调用上面的方法,也不会崩溃,亲测,但是iOS10及以下的设备会崩溃。在官方文档中没有查到相关的的说明(在iOS9之后,NSNotification已经不用移除了,可能也是这个趋势吧)。

3、实现原理

KVO是根据iOS runtime实现的,当监听某个对象(_kvoTest)的某个属性时,KVO会创建这个对象的子类,并重写我们监听的属性(keyPath)的set方法,具体实现可能是下面这个样子。

- (void)setNameStr:(NSString *)nameStr {
    [self willChangeValueForKey:@"nameStr"];
    [super setValue:nameStr forKey:@"nameStr"];
    [self didChangeValueForKey:@"nameStr"];
}

didChangeValueForKey:方法执行完之后,KVO会根据注册时option返回不同的字典

上面我们提到了KVO是建立在KVC基础上的,可以看到,如果不通过KVC改变属性,那么set方法就不会执行,那么KVO也就没办法监听属性的变化了。

4、再具体一点(原理)

我们都说他是通过runtime实现的,是因为这个操作是运行的时候动态添加的,可以多阅读一些关于runTime的文章,知道原理,用起来更顺手,也会有更多的想法。

当观察对象时,KVO机制动态创建一个新的名为:NSKVONotifying_对象名 的新类,该类继承自目标对象的本类,且 KVO 为 NSKVONotifying_对象名 重写观察属性的 set 方法。在这个过程,被观察对象的 isa 指针从指向原来的对象,被 KVO 机制修改为指向系统新创建的子类 NSKVONotifying_对象名 类,来实现当前类属性值改变的监听这也就是前面所说的“黑魔法”;我还试了以下,创建一个新的名为“NSKVONotifying_对象名”的类,发现系统运行到注册 KVO 的代码时,控制台打印警告(iOS11):

[general] KVO failed to allocate class pair for name NSKVONotifying_KVOTestModel, 
automatic key-value observing will not work for this class

存在同名类,无法进行KVO监听了,并没有像大多数文章所说,崩溃啦~。


自己动手,试一下,收获更多~


猜你喜欢

转载自blog.csdn.net/peng_up/article/details/80537461