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 *)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 *)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 *)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的本质)