使用方式
通過(guò)以下例子來(lái)總結(jié)使用方式
// 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;
// 添加觀察者為當(dāng)前控制器砾省,對(duì)age進(jìn)行觀察
NSKeyValueObservingOptions options = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
[person addObserver:self forKeyPath:@"age" options:options context:nil];
person.age = 30;
}
- (void)dealloc {
// 移除觀察者,防止內(nèi)存泄露
[self.person removeObserver:self forKeyPath:@"age"];
}
// 當(dāng)被觀察的數(shù)據(jù)變化時(shí)沸手,提醒觀察者
- (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、觀察者實(shí)現(xiàn)對(duì)應(yīng)的觀察方法(數(shù)據(jù)變化時(shí)進(jìn)行處理)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)contex
3索绪、移除觀察者(防止內(nèi)存泄露)
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
本質(zhì)分析
原來(lái)上面的例子Person中person.age = 10湖员、person.age = 20與person.age = 30在寫(xiě)法上沒(méi)有任何區(qū)別,都是調(diào)用了setter方法瑞驱。而唯有添加了觀察者后的娘摔,person.age = 30才有反應(yīng)。
從編譯時(shí)看不出來(lái)變化唤反,說(shuō)明在KVP是在runtime時(shí)動(dòng)了手腳凳寺。
在添加觀察者前后分別打斷點(diǎn),可以看到isa指針變化了彤侍。
實(shí)際上加了KVO后肠缨,對(duì)象的isa指針會(huì)重新指向 “NSKVONotifying_對(duì)象” (實(shí)際上市該類(lèi)對(duì)象的子類(lèi))
而在 “NSKVONotifying_對(duì)象”該類(lèi)對(duì)象中 重新了setter方法。
重寫(xiě)方法中實(shí)際調(diào)用過(guò)程為
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
而在didChangeValueForKey:方法中盏阶,會(huì)調(diào)用觀察者的觀察方法即[obser observeValueForKeyPath:@"age" ofObject:self change:{} content:nil];
為了證明這個(gè)過(guò)程晒奕,操作如下
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__);
}
打印結(jié)果如下
-[Person setAge:]
-[Person setAge:]
// KVO 的過(guò)程
-[Person willChangeValueForKey:]
-[Person setAge:]
-[Person didChangeValueForKey:]
-[ViewController observeValueForKeyPath:ofObject:change:context:]
-[Person didChangeValueForKey:]
上述表明KVO調(diào)用過(guò)程實(shí)際就是
[self willChangeValueForKey:@"觀察的值"];
[super setAge:值];
[self didChangeValueForKey:@"觀察的值"];
而在didChangeValueForKey:中又調(diào)用了觀察者的觀察方法即observeValueForKeyPath:ofObject:change:context方法。
問(wèn)題
1名斟、KVO本質(zhì)是什么脑慧?
本質(zhì)是改變了對(duì)象isa指針的指向并重寫(xiě)了setter方法,指向一個(gè)NSKVONotifying_對(duì)象的子類(lèi)對(duì)象砰盐。
重寫(xiě)setter方法闷袒,如下
1)willChangeValueForKey:
2)父類(lèi)原來(lái)的setter
3)didChangeValueForKey:
內(nèi)部會(huì)觸發(fā)觀察者(Oberser)的觀察方法( observeValueForKeyPath:ofObject:change:context:)
2、如何手動(dòng)觸發(fā)KVO岩梳?
手動(dòng)調(diào)用調(diào)用willChangeValueForKey:和didChangeValueForKey:方法囊骤。