KVO底层原理分析
一、 KVO内部实现原理
-
KVO是基于
runtime
机制实现的,使用了isa 混写(isa-swizzling
) -
当某个类的属性对象
第一次被观察
时,系统就会在运行期动态地创建该类的一个派生类
,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter
方法内实现真正的通知机制 -
如果原类为Person,那么生成的派生类名为
NSKVONotifying_Person
(查看方法后面讲解) -
每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
-
键值观察通知依赖于NSObject 的两个方法:
willChangeValueForKey:
和didChangevlueForKey:
;在一个被观察属性发生改变之前,willChangeValueForKey:
一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:
会被调用,继而observeValueForKey:ofObject:change:context:
也会被调用。 -
补充:KVO的这套实现机制中苹果还偷偷重写了
class
方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类
二、论证产生的子类
Book类
#import <Foundation/Foundation.h>
@interface Book : NSObject
@property (nonatomic, copy) NSString *price;
@property (nonatomic, copy) NSString *name;
@end
重写description方法便于观察产生的子类
#import "Book.h"
#import <objc/runtime.h>
@implementation Book
- (NSString *)description {
NSLog(@"object address : %p \n", self);
Class objectMethodClass = [self class];
Class objectRuntimeClass = object_getClass(self);
Class superClass = class_getSuperclass(objectRuntimeClass);
NSLog(@"objectMethodClass : %@, ObjectRuntimeClass : %@, superClass : %@ \n", objectMethodClass, objectRuntimeClass, superClass);
return @"";
}
@end
初始化调用
- (void)viewDidLoad {
[super viewDidLoad];
self.abook = [[Book alloc]init];
NSLog(@"初始化%@",self.abook);
[self.abook addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
[self.abook addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
}
打印结果如下
image.png注意:这是在绑定观察者模式之前的初始化调用,此时还没有被观察,此时并没有产生子类
触发监听调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"之前%@",self.abook);
//触发监听
[self.abook setValue:@"book" forKey:@"name"];
[self.abook setValue:@"34" forKey:@"price"];
}
打印结果如下
image.png注意:这里写了一个简单的触发事件,此时可以观察到,已经产生了一个新的类:
NSKVONotifying_Book
也可以观察到它的父类是Book
附加知识
有些时候程序逻辑的需要,一个类想要实现手动的change notification发送,则必须重写NSObject实现的automaticallyNotifiesObserversForKey:
方法,并对需要实现手动发送的key返回NO,其余则调用super。
对price属性关闭自动发送
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"price"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
对price属性实现手动的change notification发送
- (void)setPrice:(NSString *)price
{
[self willChangeValueForKey:@"price"];
_price = price;
[self didChangeValueForKey:@"price"];
}
本文部分内容转载自:https://www.jianshu.com/p/829864680648