KVO的本質(zhì)

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的方法

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容