KVO的定義
KVO 的全稱是
Key-Value Observing,俗稱“鍵值監(jiān)聽”,可以用于監(jiān)聽某個對象屬性值的改變.
圖片.png
使用
-(void)viewDidLoad
{
[super viewDidLoad];
self.people1 = [[HPPeople alloc] init];
self.people2 = [[HPPeople alloc] init];
NSKeyValueObservingOptions option = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.people1 addObserver:self forKeyPath:@"age" options:option context:nil];
}
-(void)dealloc
{
[self.people1 removeObserver:self forKeyPath:@"age"];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//people1的類對象為HPPeople
self.people1.age = 5;
//people2的類對象為NSKVONotifying_HPPeople
self.people2.age = 3;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"監(jiān)聽到%@的屬性值%@發(fā)生了變化 %@ - %@",object,keyPath,change,context);
}
點擊打印結(jié)果
監(jiān)聽到<HPPeople: 0x6000013992e0>的屬性值age發(fā)生了變化 {
kind = 1;
new = 5;
old = 0;
} - (null)
添加斷點

圖片.png
people1對象和people2對象的賦值操作,其他屬性都一樣,people1對象的age變化了會打印,而people2不會打印.然后我們打斷點發(fā)現(xiàn)people1的類對象發(fā)生了變化,變成NSKVONotifying_HPPeople
我們嘗試著打印people1對象和people2對象添加觀察者前后的setAge:方法的實現(xiàn)
修改代碼
-(void)viewDidLoad
{
[super viewDidLoad];
self.people1 = [[HPPeople alloc] init];
self.people2 = [[HPPeople alloc] init];
NSLog(@"people1未添加觀察者之前 %p %p",[self.people1 methodForSelector:@selector(setAge:)],[self.people2 methodForSelector:@selector(setAge:)]);
NSKeyValueObservingOptions option = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.people1 addObserver:self forKeyPath:@"age" options:option context:nil];
NSLog(@"people1添加觀察者之后 %p %p",[self.people1 methodForSelector:@selector(setAge:)],[self.people2 methodForSelector:@selector(setAge:)]);
}
打印結(jié)果
2020-10-16 20:22:17.118174+0800 gcd[67348:5498682] people1未添加觀察者之前 0x1040c1d30 0x1040c1d30
2020-10-16 20:22:17.118736+0800 gcd[67348:5498682] people1添加觀察者之后 0x1044478bc 0x1040c1d30
(lldb) p (IMP)0x1040c1d30
(IMP) $0 = 0x00000001040c1d30 (gcd`-[HPPeople setAge:] at HPPeople.h:15)
(lldb) p (IMP)0x1044478bc
(IMP) $1 = 0x00000001044478bc (Foundation`_NSSetLongLongValueAndNotify)
(lldb)
那么添加了觀察者對象people1的setAge:方法的實現(xiàn)變成Foundation框架的_NSSetLongLongValueAndNotify函數(shù)
我們通過反編譯工具Hopper拿到Foundation框架的偽代碼從中去查找_NSSetLongLongValueAndNotify函數(shù)看到的是匯編代碼,大致的實現(xiàn)過程如下偽代碼
-(void)_NSSetLongLongValueAndNotify //偽代碼
{
[self willChangeValueForKey:@"age"];
//中間是原來setter的實現(xiàn)
[self didChangeValueForKey:@"age"];
}
- 調(diào)用willChangeValueForKey:
- 調(diào)用原來的setter實現(xiàn)
- 調(diào)用didChangeValueForKey:didChangeValueForKey:內(nèi)部會調(diào)用observer的observeValueForKeyPath:ofObject:change:context:方法
KVO的本質(zhì)用圖來展示如下

圖片.png
結(jié)論:
KVO的本質(zhì)是運用RuntimeApi動態(tài)的生成了一個子類,并讓這個instance對象的isa指針指向這個全新的類,當修改這個instance對象的屬性時就會調(diào)用全新子類的setter方法實現(xiàn),這個setter方法的實現(xiàn)是調(diào)用了Fundation框架_NSSetXXXValueAndNotify的方法
