一.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)聽對象
options
:NSKeyValueObservingOptionNew
(提供更改前的值) 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é)果:
二.KVO剖析
我們重新定義一個(gè)GYPerson
的對象person2
,不對person2
中的任何屬性進(jìn)行監(jiān)聽。分別打印person1
與person2
的isa
指針來觀察他們的類
可以觀察到他們的所屬的類已經(jīng)不一樣的,添加監(jiān)聽的
person1
屬于NSKVONotifying_GYPerson
類,未添加監(jiān)聽的person2
仍屬于GYPerson
類。或者
我們使用
RunTime
的object_getClass
方法查看
NSLog(@"person1的Class%@", object_getClass(self.person1));
NSLog(@"person2的Class%@", object_getClass(self.person2));
如圖:
同樣說明剛剛的驗(yàn)證
原因:在對一個(gè)類的對象添加監(jiān)聽后,系統(tǒng)會利用Runtime
API動態(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:)]);
可以觀察到:person2
的setAge:
方法在GYPerson
中,而person1
的setAge:
方法卻在foundation
中的_NSSetLongLongValueAndNotify
中。
2.對生成的子類NSKVONotifying_GYPerson
類中setAge:
方法猜測
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:方法 )
結(jié)果
Tips
1.在NSKVONotifying_GYPerson
類中,重寫了以下方法,從而讓開發(fā)者不會產(chǎn)生過多疑惑。改新生成子類中包含一些其他方法,可以通過RunTime
的class_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)用willChangeValueForKey
和didChangeValueForKey
方法,但是這兩個(gè)方法需要成對出現(xiàn),盡管觸發(fā)observeValueForKeyPath
的操作是在didChangeValueForKey
中,可以理解為父類中添加了相關(guān)的判斷。