OC

KVO和KVC的本质

Posted by sunzhongliang on September 18, 2019

KVO

什么是KVO

KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变

KVO的用法

self.person1 = [[MyPerson alloc] init];
self.person1.age = 1;
self.person1.height = 11;

// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];

// 当监听对象的属性值发生改变时,就会调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}

// 页面销毁时,移除监听对象
- (void)dealloc {
    [self.person1 removeObserver:self forKeyPath:@"age"];
}
// 打印输入:
监听到<MyPerson: 0x600002390680>age属性值改变了 - {
    kind = 1;
    new = 20;
    old = 1;
} - 123

KVO的本质

当给对象添加KVO监听后,po self.person1 ->isa查看对象的isa指针,这时候会变为NSKVONotifying_MyPerson;和没添加KVO监听前的isa指针的对象发生了变化

未使用KVO监听的对象,调用属性set方法时
调用对象属性set方法时,会通过isa指针找到类对象,在类对象里面直接找set方法的实现

使用KVO监听的对象,调用属性set方法时
调用对象属性set方法时,会通过isa指针找到NSKVONotifying_MyPerson,然后在NSKVONotifying_MyPerson对象里面找set方法实现,set方法会调用Foundation框架的NSSetIntValueAndNotify

验证是否存在NSKVONotifying_MyPerson类,可手动创建一个相同名字的NSKVONotifying_MyPerson,这时候运行项目调用对象set方法,编译器就会有警告输出:KVO failed to allocate class pair for name NSKVONotifying_MyPerson, automatic key-value observing will not work for this class 这也从侧面证明了KVO动态生成了这样一个类对象

通过lldb指令查看IMP,可以很明显的看出KVO最终的调用方法:

// ----------------打印添加了KVO的对象,看看类对象和元类对象分别是什么
NSLog(@"类对象 - %@ %@",
object_getClass(self.person1),  // self.person1.isa

object_getClass(self.person2)); // self.person2.isa


NSLog(@"元类对象 - %@ %@",
object_getClass(object_getClass(self.person1)), // self.person1.isa.isa

object_getClass(object_getClass(self.person2))); // self.person2.isa.isa

// --------输出-------
类对象 - NSKVONotifying_MyPerson MyPerson
元类对象 - NSKVONotifying_MyPerson MyPerson
// ---因此添加了KVO后,类对象会变为NSKVONotifying_MyPerson,NSKVONotifying_MyPerson的isa指针指向同一个元类对象

KVO原理

苹果使用了isa混写技术(isa-swizzling)来实现KVO,当我们调用addObserver:forKeyPath:options:context:方法时,系统会为我们监听的instance对象Runtime api动态创建一个NSKVONotifying_XXXX子类,并且让这个监听的instance对象的isa指针,指向这个全新的子类,并且重写被监听的类属性的setter方法来达到可以通知所有观察者对象的目的

当移除KVO监听后,被观察对象的isa会指回原类A,但是NSKVONotifying_XXXX类并没有销毁,还保存在内存中。

_NSSet*ValueAndNotify的内部实现

  • 调用willChangeValueForKey:
  • 调用原来的setter实现
  • 调用didChangeValueForKey:
    • didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法

FBKVOController

系统的KVO使用起来比较麻烦,需要添加监听、实现监听、移除监听
FBKVOControllerFacebook 开源的一个基于系统KVO实现的框架。支持Objective-CSwift语言
github: https://github.com/facebook/KVOController

willChange和didChange的不同点

willChange接下来会调用setter方法,而didChange接下来会调用对象的observeValueForKeyPath:ofObject:change:context:方法

FBKVOController 的优点

  • 会自动移除观察者
  • 函数式编程,一行代码实现KVO监听的三个步骤
  • 线程安全
  • 每一个keypath会对应一个block或者SEL,不需要使用if判断keypath

使用方法:

// create KVO controller with observer
FBKVOController *KVOController = [FBKVOController controllerWithObserver:self];
self.KVOController = KVOController;

// observe clock date property
// 使用 block
[self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) {

  // update clock view with new value
  clockView.date = change[NSKeyValueChangeNewKey];
}];

// 使用 SEL
[self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew action:@selector(updateClockWithDateChange:)];

KVC

什么是KVC

KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性

KVC的用法

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key; 

通过KVC修改属性会触发KVO么?

能够触发

KVC setValue:forKey:的原理

KVC valueForKey:的原理

本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.