一晌梨、KVO
KVO 的作用:
kvo 就是監(jiān)聽某個對象的屬性桥嗤,在該屬性的值發(fā)生變化時,通知觀察者仔蝌。
KVO 的簡單實用
- (void)viewDidLoad {
[super viewDidLoad]
self.testKvo = [[TestKVO alloc]init];
self.testKvo2 = [[TestKVO alloc]init];
self.testKvo.name = @"testKvo初始值";
self.testKvo2.name = @"testKvo2初始值";
[self.testKvo addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:@"123"];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.testKvo.name = @"testKvo改變值";
self.testKvo2.name = @"testKvo2改變值";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];
NSLog(@"object = %@ testKvo = %@ testKvo2 =%@",object,self.testKvo,self.testKvo2);
NSLog(@"oldvalue = %@",oldValue);
NSLog(@"newvalue = %@",newValue);
NSLog(@"context = %@",context);
}
我們運行泛领,可以看到結果:
參數說明:
- addObserver:監(jiān)聽器,誰來監(jiān)聽
- forKeyPath:要監(jiān)聽的屬性
- options:一般是 NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew敛惊,新值和舊值
- context:添加監(jiān)聽時渊鞋,傳入的時候,在收到監(jiān)聽時瞧挤,可以獲得锡宋,從上面的代碼和log日志,我們也可以看到
分析:
- 對象調了 setter 方法特恬,監(jiān)聽器就會收到監(jiān)聽的回調
- 上面的代碼中执俩,兩個同類型的對象,在都調了setter 方法時癌刽,只有 testKvo 收到了回調役首,而 testKvo2 并沒有收到回調
由此,我們可以看到显拜,同一個類型的對象在都調了setter 方法時衡奥,只有添加了監(jiān)聽的對象才會收到監(jiān)聽的回調。
探究過程
- 我們知道對象方法存放在類對象中远荠,既然都是調的是 setter 方法矮固,為什么testKvo2 就沒有收到監(jiān)聽呢,我們可以猜想到譬淳,這兩個對象的 setter 是不是不一樣档址,添加監(jiān)聽了的對象的setter 方法被系統(tǒng)內部改變了呢盹兢?
- 那我們用runtime 來打印一下這兩個對象的類對象是不是有什么變化。
注意:使用 Class 方法獲取的對象類型不是準確的辰晕,我們也可以猜到蘋果這么做是不想給我們暴露這個類,想要獲取類的真實類型使用runtime 的 object_getClass()函數
NSLog(@"添加監(jiān)聽前 testKvo = %@ testKvo2= %@",object_getClass(self.testKvo),object_getClass(self.testKvo2));
[self.testKvo addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:@"123"];
NSLog(@"添加監(jiān)聽后 testKvo = %@ testKvo2= %@",object_getClass(self.testKvo),object_getClass(self.testKvo2));
- 我們可以看到确虱,在添加了監(jiān)聽后的對象含友,類型發(fā)生了改變,變成了
NSKVONotifying_TestKVO
,而沒有添加監(jiān)聽類型并沒有改變校辩。 - 由此窘问,我們可以看出kvo的實現原理是:創(chuàng)建了一個前綴為NSKVONotifying_ 新的類,并且這個類繼承監(jiān)聽對象原來的類宜咒,并且重寫了setter 方法惠赫,在對象調了setter 方法時,通知監(jiān)聽器故黑,實現回調儿咱。
-為什么我們說是重寫了 setter 方法,我們使用 runtime 的函數:methodForSelector 來看下到底是不是
NSLog(@"添加監(jiān)聽前 testKvo = %p testKvo2= %p",[self.testKvo methodForSelector:@selector(setName:)],[self.testKvo2 methodForSelector:@selector(setName:)]);
[self.testKvo addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:@"123"];
NSLog(@"添加監(jiān)聽后 testKvo = %p testKvo2= %p",[self.testKvo methodForSelector:@selector(setName:)],[self.testKvo2 methodForSelector:@selector(setName:)]);
我們看到场晶,在 添加監(jiān)聽前 兩個對象的 setter 方法是同一個混埠,而在添加監(jiān)聽后的對象的setter 方法地址發(fā)生了改變。而且我們在調試環(huán)境下還可以打印出setter 方法的相關信息诗轻。添加監(jiān)聽前是setName钳宪,而添加監(jiān)聽后方法是 _NSSetObjectValueAndNotify ,而且是屬于 Foundation 框架下的方法扳炬。由此吏颖,我們可以知道確實是重寫了setter 方法。
kVO 實現原理總結:
- 動態(tài)生成一個子類恨樟,讓這個對象的isa 指針指向一個新的類(這個新的類集成自原來的類)
- 當修改對象的屬性時 會調用重寫的setter 方法半醉。這個 setter 方法內部是 先調willChangeValueForKey, 再調父類原來的setter方法劝术,再調didChangeValueForKey奉呛,最后觸發(fā)監(jiān)聽的回調方法
補充:
- 直接修改成員變量是不會觸發(fā) kvo 監(jiān)聽的
- 使用 kvc 是會觸發(fā) kvo 監(jiān)聽的(原因是,使用kvc ,也會來到setter 方法夯尽。kvc 內部調用了 willChangeValueForKey 和 didChangeValueForKey 方法)
二瞧壮、 KVC
基本使用
- setValue:forKey 和 setValue:forKeyPath兩個方法
- setValue forkey 和 setValue forKeyPath 的區(qū)別在于,forKeyPath 是可以多深層次訪問的匙握。例如:有兩個類 Psrson 和Student咆槽,Psrson 類里面有個Student 類型的對象,Student 類里面有個 score 屬性圈纺。那么就可以這么使用:[person setValue:@80 forKeyPath:@"student.score" ]
setValue 設值順序
查找setKey 方法秦忿,有則調用麦射,沒有則查找看有沒有_setKey方法,有則調用灯谣。沒有則查看 accessInstanceVariablesDirectly 這個類方法的返回值潜秋,是yes則按照_key 、_isKey 胎许、key 峻呛、iskey 順序查找成員變量。如果沒有找到則拋出異常 NSUnknownKeyException
辜窑。如果accessInstanceVariablesDirectly返回值是 NO钩述,則程序拋出異常
測試setValue 設值順序
- 新建一個名為 TestKVC的類,
并且沒有給這個類添加為name的屬性
穆碎,在這個類里面添加setName 牙勘、_setName方法和 _name、isName所禀、name 方面、isName四個成員變量
TestKVC *kvc = [[TestKVC alloc]init];
[kvc setValue:@"測試名字" forKey:@"name"];
- (void)setName:(NSString *)name
{
NSLog(@"%s %@",__func__,name);
}
- (void)_setName:(NSString *)name
{
NSLog(@"%s %@",__func__,name);
}
-
我們可以看到結果如下:
- 然后我們
注釋setName 方法
看結果:
由此可見,kvc 確實是按照 setKey 和_setKey 方法查找的色徘。 - 接下來葡幸,我們注釋掉setName 和_setName 方法,在里面實現 accessInstanceVariablesDirectly 類方法再來看看結果
+ (BOOL)accessInstanceVariablesDirectly
{
return NO;
}
- 這個時候贺氓,程序會直接拋出異常蔚叨,我們將返回結果改成 YES繼續(xù)看看結果
-
我們可以看到是先給_name 賦值的,然后我們** 依次注釋 _name 辙培、 isName 蔑水、name 、isName 查看結果
-
我們可以看到扬蕊,kvc 確實是按 _name 搀别、 isName 、name 尾抑、isName 查找的歇父。
valueForKey 的取值順序
- 先按照
getKey 、key 再愈、isKey 榜苫、_key
順序查找方法取值,如果沒有找到方法則查看accessInstanceVariablesDirectly 類方法的返回值翎冲,如果返回的是 NO垂睬,則拋出異常。如果返回的是YES,按照_key 驹饺、_isKey 钳枕、key 、isKey
順序查找成員變量赏壹,如果沒有找到鱼炒,則拋出異常。
測試過程
參照上面valueForkey 取值的方式蝌借,可以驗證是否正確昔瞧。這里就不贅述了。
這些就是在最近學習探究實現原理的全部內容和過程骨望,有問題或者疑問的歡迎提出硬爆,共同進步欣舵,謝謝 ~