iOS底层原理 -- KVO的本质

使用方式

通过以下例子来总结使用方式

// ViewController.h
#import "ViewController.h"
#import "Person.h"

@interface ViewController ()
@property (nonatomic, strong) Person *person;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person = [[Person alloc] init];
    self.person = person;
    person.age = 10;
    person.age = 20;
    
    // 添加观察者为当前控制器,对age进行观察
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
    [person addObserver:self forKeyPath:@"age" options:options context:nil];
    
    person.age = 30;
}

- (void)dealloc {
    // 移除观察者,防止内存泄露
    [self.person removeObserver:self forKeyPath:@"age"];
}

// 当被观察的数据变化时,提醒观察者
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"改变的值---- %@", change);
    
}


@end

result: 
改变的值---- {
    kind = 1;
    new = 30;
    old = 20;
}

使用方式:
1、添加观察者

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

2、观察者实现对应的观察方法(数据变化时进行处理)

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)contex

3、移除观察者(防止内存泄露)

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

本质分析

原来上面的例子Person中person.age = 10、person.age = 20与person.age = 30在写法上没有任何区别,都是调用了setter方法。而唯有添加了观察者后的,person.age = 30才有反应。
从编译时看不出来变化,说明在KVP是在runtime时动了手脚。
在添加观察者前后分别打断点,可以看到isa指针变化了。

添加观察者前.png
添加观察后.png

实际上加了KVO后,对象的isa指针会重新指向 “NSKVONotifying_对象” (实际上市该类对象的子类)
而在 “NSKVONotifying_对象”该类对象中 重新了setter方法。
重写方法中实际调用过程为

[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];

而在didChangeValueForKey:方法中,会调用观察者的观察方法即[obser observeValueForKeyPath:@"age" ofObject:self change:{} content:nil];

为了证明这个过程,操作如下
Person.m文件中以下方法

// Person.m
- (void)willChangeValueForKey:(NSString *)key {
    NSLog(@"%s", __func__);
    [super willChangeValueForKey:key];
}

- (void)setAge:(int)age {
    NSLog(@"%s", __func__);
    _age = age;
}

- (void)didChangeValueForKey:(NSString *)key {
    NSLog(@"%s",  __func__);
    [super didChangeValueForKey:key];
    NSLog(@"%s", __func__);
}

ViewController.m文件中以下方法

//  ViewController.m
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%s", __func__);    
}

打印结果如下

-[Person setAge:]
-[Person setAge:]
// KVO 的过程
-[Person willChangeValueForKey:]
-[Person setAge:]
-[Person didChangeValueForKey:]
-[ViewController observeValueForKeyPath:ofObject:change:context:]
-[Person didChangeValueForKey:]

上述表明KVO调用过程实际就是

[self willChangeValueForKey:@"观察的值"];
[super setAge:值];
[self didChangeValueForKey:@"观察的值"];

而在didChangeValueForKey:中又调用了观察者的观察方法即observeValueForKeyPath:ofObject:change:context方法。

问题

1、KVO本质是什么?
本质是改变了对象isa指针的指向并重写了setter方法,指向一个NSKVONotifying_对象的子类对象。
重写setter方法,如下
1)willChangeValueForKey:
2)父类原来的setter
3)didChangeValueForKey:
内部会触发观察者(Oberser)的观察方法( observeValueForKeyPath:ofObject:change:context:)

2、如何手动触发KVO?
手动调用调用willChangeValueForKey:和didChangeValueForKey:方法。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 问题 iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?) 如何手动触发KVO ? 首先需要了解KVO...
    hjltony阅读 586评论 0 2
  • 面试问题: · iOS用什么方式实现对一个对象的KVO? · 如何手动触发KVO? 我们通过以下几个点来寻找这两个...
    高思阳阅读 252评论 0 1
  • KVC KVC定义 KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过K...
    暮年古稀ZC阅读 2,173评论 2 9
  • 对小码哥底层班视频学习的总结与记录。面试题部分,通过对面试题的分析探索问题的本质内容。 问题iOS用什么方式实现对...
    xx_cc阅读 10,832评论 26 65
  • 一、KVO 的使用 KVO 的全称 Key-Value Observing,俗称“键值监听”,可以用于监听某个对象...
    666真666阅读 688评论 0 1