關(guān)于KVO分析總結(jié)筆記

一.KVO基礎(chǔ)

KVO的全稱是Key-Value Observing,俗稱鍵值監(jiān)聽,可以用于監(jiān)聽某個(gè)對象屬性值的改變
通過
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
方法給類的某個(gè)對象添加監(jiān)聽。

在監(jiān)聽類中實(shí)現(xiàn)
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context
進(jìn)行監(jiān)聽

observer:監(jiān)聽方
keyPath:監(jiān)聽對象
optionsNSKeyValueObservingOptionNew(提供更改前的值) NSKeyValueObservingOptionOld(提供更改后的值)
NSKeyValueObservingOptionInitial (觀察最初的值,再注冊觀察服務(wù)時(shí)會調(diào)用一次觸發(fā)方法)
NSKeyValueObservingOptionPrior(分別在值修改前后觸發(fā)方法,即一次修改有兩次觸發(fā))
context:傳入的內(nèi)容

示例:

self.person1 = [[GYPerson alloc] init];
self.person1.age = 10;
[self.person1 addObserver:self
                  forKeyPath:@"age"
                     options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
                     context:@"123"];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@ - %@ - %@ - %@", keyPath, object, change, context);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.person1.age = 100;
}

運(yùn)行結(jié)果:


1.png

二.KVO剖析

我們重新定義一個(gè)GYPerson的對象person2,不對person2中的任何屬性進(jìn)行監(jiān)聽。分別打印person1person2isa指針來觀察他們的類

2.png

可以觀察到他們的所屬的類已經(jīng)不一樣的,添加監(jiān)聽的person1屬于NSKVONotifying_GYPerson類,未添加監(jiān)聽的person2仍屬于GYPerson類。
或者
我們使用RunTimeobject_getClass方法查看

NSLog(@"person1的Class%@", object_getClass(self.person1));
NSLog(@"person2的Class%@", object_getClass(self.person2));

如圖:


3.png

同樣說明剛剛的驗(yàn)證

原因:在對一個(gè)類的對象添加監(jiān)聽后,系統(tǒng)會利用RuntimeAPI動態(tài)生成一個(gè)子類,并且讓instance對象的isa指向這個(gè)全新的子類。全新的子類中重新實(shí)現(xiàn)了setAge:方法,setAge:方法實(shí)現(xiàn)了foundation中的_NSSetIntValueAndNotify方法

驗(yàn)證:

1.重寫setAge:方法驗(yàn)證:

我們方別打印方法地址

NSLog(@"person1的setAge:方法地址 %p", [self.person1 methodForSelector:@selector(setAge:)]);
NSLog(@"person2的setAge:方法地址 %p", [self.person2 methodForSelector:@selector(setAge:)]);

可以觀察到:person2setAge:方法在GYPerson中,而person1setAge:方法卻在foundation中的_NSSetLongLongValueAndNotify中。

4.png

2.對生成的子類NSKVONotifying_GYPerson類中setAge:方法猜測
5.png
3.驗(yàn)證_NSSetIntValueAndNotify中的調(diào)用流程:

我們可以在GYPerson中實(shí)現(xiàn)重寫 willChangeValueForKey:didChangeValueForKey:方法,打印log,觀察調(diào)用順序。(didChangeValueForKey:內(nèi)部會調(diào)用observer的observeValueForKeyPath:ofObject:change:context:方法 )

6.png

結(jié)果
7.png

Tips

1.在NSKVONotifying_GYPerson類中,重寫了以下方法,從而讓開發(fā)者不會產(chǎn)生過多疑惑。改新生成子類中包含一些其他方法,可以通過RunTimeclass_copyMethodList及其他函數(shù)查看或驗(yàn)證。

// 內(nèi)部實(shí)現(xiàn),隱藏了NSKVONotifying_MJPerson類的存在 [self.person class]獲取出的類名都是GYPerson
- (Class)class {
    return [GYPerson class];
}

2.在工程中手動創(chuàng)建NSKVONotifying_GYPerson類,會導(dǎo)致addObserver失敗

3.手動觸發(fā)KVO的方法,其實(shí)就是手動調(diào)用willChangeValueForKeydidChangeValueForKey方法,但是這兩個(gè)方法需要成對出現(xiàn),盡管觸發(fā)observeValueForKeyPath的操作是在didChangeValueForKey中,可以理解為父類中添加了相關(guān)的判斷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容