KVO的一般使用場景:
- (void)viewDidLoad {
[super viewDidLoad];
DuPerson *p = [[DuPerson alloc] init];
p.age = 1;
[p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
p.age = 3;
}
- (void)dealloc{
[self.p removeObserver:self forKeyPath:@"age"];
}
/ * @param keyPath 被修改的屬性
* @param object 被修改的屬性所屬的對象
* @param change 屬性改變情況(新舊值)
* @param context void * == id
* /
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
NSLog(@"%@對象的%@屬性改變了:%@",object,keyPath,change);
}
/************************ NSLog ***************************/
<DuPerson: 0x7fc5a3519920>對象的age屬性改變了:{
kind = 1;
new = 3;
old = 1;
}
KVO的實現原理
疑問
上述的DuPerson
的age
屬性的值改變了,為什么能調用observeValueForKeyPath: ofObject: change: context:
方法?原理
系統會動態的生成一個繼承于DuPerson
的子類,如NSKVONOtifying_DuPerson
,然后在該類里面重寫setAge:
方法,在該子類的setAge:
方法實現通知機制,讓監聽器來調用observeValueForKeyPath: ofObject: change: context:
方法來進行監聽,所以這里就解決了上面的疑問了。但是又產生了另外一個新的疑問,就是:系統動態生成的NSKVONOtifying_DuPerson
類與DuPerson
有什么關系?按道理來說,修改p的age屬性也只會調用DuPerson
類的setAge:
方法,但是為什么會執行了NSKVONOtifying_DuPerson
的setAge:
方法呢?其實這里其關鍵作用的就是KVO了,那么我們來看看具體的代碼實現無KVO的情況下
DuPerson *p = [[DuPerson alloc] init];
p.age = 1;
p.age = 3;
通過斷點調試可以看到p里面的isa
指針指向的是DuPerson
,如下圖
沒實現KVO
但是
isa
又是什么東西了,先不著急,我們再來看看有KVO的情況下
- 有KVO的情況下
DuPerson *p =[[DuPerson alloc] init];
p.age = 2;
[p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
p.age = 5;
通過斷點調試可以看到p里面的isa
指針指向的是NSKVONOtifying_DuPerson
,也就是說p指針指向的對象的類型應經發生改變了
實現KVO
- isa指針簡介
其實isa指針是每個對象結構體的首個成員,它是Class類的變量,Class isa
,這個變量定義了對象所屬的類,所以我們可以通過這個isa
變量來查詢對象能否響應某個方法,因為一個對象的方法是存放在它所屬的類當中,所以我們就可以通過isa
變量來找到所屬的類,并在該類的方法列表里面尋找對應的方法來實現。
好了,此時我們就可以解決上述的問題了。
通過KVO監聽后,p指針指向的對象的類型已經發生了改變了,isa
指針原來指向DuPerson
變為NSKVONOtifying_DuPerson
了,所以接下來p.age = 3
的時候,它調用的不再是DuPerson
類的setAge:
方法,而是NSKVONOtifying_DuPerson
類的setAge:
方法,因為p指針指向的對象通過isa
指針找到它的父類,然后再在父類的方法列表里面找到對應的方法來進行實現了。 -
NSKVONOtifying_DuPerson
類重寫setAge:
方法
- (void)setAge:(NSInteger)age{
[super setAge:age];
// [監聽器 observeValueForKeyPath:@"age" ofObject:self change:@{} context:nil];
}
- 簡單的總結:表面看起來是屬于A類,底層實現卻是屬于B類。就例如
NSMutableArray
底層的真實類型是_NSArrayM
一樣。