浅谈iOS KVO

1、KVO<Key-Value-Observing>

顾名思义,键值监听,可以用于监听某个对象属性值的变化。KVO是一个非正式协议,提供了一个途径,使对象(观察者)能够观察其他对象(被观察者)的属性,当被观察者的属性发生变化时,观察者就会被告知该变化。首先了解一下KVO的基本使用,然后在此基础上,我们深入了解一下KVO的底层实现原理。

//给一个对象属性添加KVO监听
[self addObserver:(nonnull NSObject *)
       forKeyPath:(nonnull NSString *)
          options:(NSKeyValueObservingOptions)
          context:(nullable void *)]
//当监听对象的属性值发生改变时,就会调用
-(void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                       change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                      context:(void *)context
//需要在不使用的时候,移除监听
- (void)dealloc{
    [self removeObserver:self forKeyPath:@""];
}

2、KVO的底层实现原理

在了解KVO底层之前,我们先要对isa一些基本的概念有个了解,instance对象的isa指向class对象,class对象的isa指向Meta-Class对象,Meta-Class对象的isa指向基类的Meta-Class对象。如果不了解的可以去看一下我的另一篇文章《通俗易懂的ios方法调用底层原理》

KVO 是基于Runtime机制实现的,KVO是运用了一个isa-swizzling 技术,就是类型混合指针机制, 将2个对象的isa指针互相调换, 就是俗称的黑魔法.

  • 当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制

  • 如果原类为Person,那么生成的派生类名为NSKVONotifying_Person

  • 每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法

  • 调用非监听属性设置方法,会通过 NSKVONotify_Person 的 superclass,找到 Person 类对象,再调用其 实例方法

  • 键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。

  • 补充:KV0的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类。如果没有重写class方法,当该对象调用class方法时,会在自己的方法缓存列表,方法列表,父类缓存,方法列表一直向上去查找该方法,因为class方法是NSObject中的方法,如果不重写最终可能会返回NSKVONotifying_Person,就会将该类暴露出来,也给开发者造成困扰,写的是Person,添加KVO之后class方法返回怎么是另一个类。

3、kvo和 notification(通知)的区别?

  • KVO 和 NSNotificationCenter 都是 iOS 中观察者模式的一种实现。区别在于,相对于被观察者和观察者之间的关系,KVO 是一对一的,而不是一对多的。KVO 对被监听对象无侵入性,不需要修改其内部代码即可实现监听。

  • notification 的优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,而且可以一对多。

4、测试代码 

NSKeyValueObservingOptions option = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
Person *person1 = [Person new];

NSLog(@"person1添加KVO监听对象之前-类对象 -%@", object_getClass(person1));
NSLog(@"person1添加KVO监听之前-方法实现 -%p", [person1 methodForSelector:@selector(setAge:)]);
NSLog(@"person1添加KVO监听之前-元类对象 -%@", object_getClass(object_getClass(person1)));

[person1 addObserver:self forKeyPath:@"age" options:option context:@"age chage"];

NSLog(@"person1添加KVO监听对象之后-类对象 -%@", object_getClass(person1));
NSLog(@"person1添加KVO监听之后-方法实现 -%p", [person1 methodForSelector:@selector(setAge:)]);
NSLog(@"person1添加KVO监听之后-元类对象 -%@", object_getClass(object_getClass(person1)));

//打印结果
KVO-test[1214:513029] person1添加KVO监听对象之前-类对象 -Person
KVO-test[1214:513029] person1添加KVO监听之前-方法实现 -0x100411470
KVO-test[1214:513029] person1添加KVO监听之前-元类对象 -Person

KVO-test[1214:513029] person1添加KVO监听对象之后-类对象 -NSKVONotifying_Person
KVO-test[1214:513029] person1添加KVO监听之后-方法实现 -0x10076c844
KVO-test[1214:513029] person1添加KVO监听之后-元类对象 -NSKVONotifying_Person

猜你喜欢

转载自blog.csdn.net/shuzhi57/article/details/114300135