KVO的底层分析

2019-08-12  本文已影响0人  CoderGuogt

KVO底层分析

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

基本使用

添加一个成员变量

@property (nonatomic, strong) YXCPerson *person;
- (void)viewDidLoad {
    [super viewDidLoad];
    
    _person = [YXCPerson new];
    _person.name = @"Jack";
    
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [_person addObserver:self forKeyPath:@"name" options:options context:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    self.person.name = @"Tom";
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    NSLog(@"%@的%@属性发生了改变:%@", object, keyPath, change);
}

点击模拟器,输出结果

2019-08-12 11:22:27.251736+0800 KVO[18649:153721] <YXCPerson: 0x600002a1c410>的name属性发生了改变:{
    kind = 1;
    new = Tom;
    old = Tom;
}

以上就是KVO的基本使用

为什么添加一个observe就能监听到属性的改变?再新建一个Person实例对象,不添加observe,通过同样的方式修改name属性的值,为什么添加了监听的实例对象就能监听?

接下来,再新建一个Person实例对象,代码如下

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _person1 = [YXCPerson new];
    _person1.name = @"Jack";
    
    self.person2 = [YXCPerson new];
    self.person2.name = @"Tony";
    
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [_person1 addObserver:self forKeyPath:@"name" options:options context:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    self.person1.name = @"Tom";
    self.person2.name = @"Kebi";
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    NSLog(@"%@的%@属性发生了改变:%@", object, keyPath, change);
}

在添加监听之前,之后输出Person1Person2的类型

2019-08-12 11:32:55.876017+0800 KVO[19438:193756] 添加监听之前:self.person1 : YXCPerson, self.person2 : YXCPerson
2019-08-12 11:32:55.876745+0800 KVO[19438:193756] 添加监听之后:self.person1 : NSKVONotifying_YXCPerson, self.person2 : YXCPerson

再看看NSKVONotifying_YXCPerson的父类是哪个类对象

NSLog(@"person1的父类是:%@", [object_getClass(self.person1) superclass]);

输出结果:

2019-08-12 11:37:31.339343+0800 KVO[19513:198711] person1的父类是:YXCPerson

在这里发现,添加了监听之后,Person1的类型变成了NSKVONotifying_YXCPerson,这是利用Runtime API 动态生成了额一个子类,并且让添加了监听的实例对象 isa指向了这个全新的子类.

通过运行时获取 NSKVONotifying_YXCPerson 这个类的方法列表

- (void)printfMethodNameWithClass:(Class)class {
    
    unsigned int count;
    // 获得方法数组
    Method *methodList = class_copyMethodList(class, &count);
    
    // 存储方法名
    NSMutableString *methodNames = [NSMutableString string];
    
    // 遍历所有的方法
    for (int i = 0; i < count; i++) {
        // 获得方法
        Method method = methodList[i];
        // 获得方法名
        NSString *methodName = NSStringFromSelector(method_getName(method));
        // 拼接方法名
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    
    // 释放
    free(methodList);
    
    // 打印方法名
    NSLog(@"%@ %@", class, methodNames);
}

输出结果

NSKVONotifying_YXCPerson setName:, class, dealloc, _isKVOA,

通过输出结果,可以发现这个全新的子类有四个方法 setName: class dealloc _isKVOA

在通过全新的子类的setName:方法的地址,获取到实际上setName:的方法

NSLog(@"%p", [self.person1 methodForSelector:@selector(setName:)]);

0x10a3beb5e
(lldb) p (IMP)0x10a3beb5e
(IMP) $0 = 0x000000010a3beb5e (Foundation`_NSSetObjectValueAndNotify)

这是发现 NSKVONotifying_YXCPerson 这个全新的类,在调用 setName:这个方法的时候,使用的是 Fundation 框架的一个 _NSSetObjectValueAndNotify C 语言方法

_NSSetObjectValueAndNotify 的内部实现

调用 willChangeValueForKey:
调用父类的 setter 实现方法
调用 didChangeValueForKey: (内部会调用observer的observeValueForKeyPath:ofObject:change:context方法)

也可以通过

[self.person1 willChangeValueForKey:@"name"];
[self.person1 didChangeValueForKey:@"name"];

手动触发监听

上一篇下一篇

猜你喜欢

热点阅读