前言
iOS 底層第20
天的學(xué)習(xí)密似。今天分享的內(nèi)容是 KVO
一些你從未涉及到的東西抖仅。
What is KVO ?
- 查看 蘋果官方文檔
- KVO :
Key-Value Observing
是一種機(jī)制纺座,當(dāng)其他對(duì)象的特定屬性發(fā)送變化時(shí)會(huì)通知某個(gè)對(duì)象
KVO 如何使用
- 使用
KVO
硝枉,首先你必須確定被觀察的對(duì)象是否支持。如果你的對(duì)象繼承NSObject
且通過常規(guī)的方式創(chuàng)建屬性缠黍。你的對(duì)象和屬性會(huì)自動(dòng)支持KVO
弄兜,也可以手動(dòng)支持。 - 其次,你需要把觀察者
Person
注冊(cè)到被觀察者的Account
上替饿。Person
發(fā)送一個(gè)addObserver:forKeyPath:options:context:
消息給Account
- 為了能夠接受到來自
Account
的消息语泽,Person
觀察者需要實(shí)現(xiàn)一個(gè)接口observeValueForKeyPath:ofObject:change:context
,Account
將會(huì)發(fā)送一個(gè)消息給Person
在任何時(shí)間點(diǎn)keyPath
發(fā)生變化视卢。Person
也能根據(jù)通知的變化采取相應(yīng)的措施踱卵。
- 最后,當(dāng)不再需要通知時(shí)据过,至少在觀察者
deallocated
之前通過Person
對(duì)象移除冊(cè)從Account
發(fā)送消息removeObserver:forKeyPath:
- 小結(jié):
kvo
三部曲- 第一步:
receive observer
- 第二步:
observed property of an object changes
- 第三步:
remove observer
- 第一步:
第一步:receive observer
[self.xk_student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
第二步: observed property of an object changes
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"change %@",change);
}
第三步:remove observer
- (void)dealloc{
[self.xk_student removeObserver:self forKeyPath:@"name"];
}
- 給
name
進(jìn)行賦值
self.xk_student.name = @"emiya";
- 打印輸出??
addObserver 參數(shù)分析
Options
- 可傳入
4
個(gè)類型惋砂,分別是??
值 | 功能 |
---|---|
NSKeyValueObservingOptionNew |
作為變更信息的一部分發(fā)送新值 |
NSKeyValueObservingOptionOld |
作為變更信息的一部分發(fā)送舊值 |
NSKeyValueObservingOptionInitial |
在注冊(cè)時(shí)發(fā)送一個(gè)初始更新 |
NSKeyValueObservingOptionPrior |
在變更前后分別發(fā)送變更,而不只在變更后發(fā)送一次 |
Context
- 先來看一下官方的解釋 ??
大致的意思就是:
context pointer
在更改通知中傳遞回觀察者的任意數(shù)據(jù)蝶俱,您可以指定NULL
和key path
來確定更改通知的來源,但是這種方法可能會(huì)導(dǎo)致對(duì)象出現(xiàn)問題班利,因?yàn)槠?code>超類出于不同的原因也會(huì)觀察同一個(gè)key path
。
一種更安全榨呆、更可擴(kuò)展的方法是使用Context
來確保您收到的通知是針對(duì)您的觀察者而不是超類的罗标。
- 寫個(gè)?? 看一下
self.xk_student = [XKStudent new];
self.xku_student = [XKUniversityStudent new];
// XKUniversityStudent 繼承 XKStudent
// 給 xk_student 和 xku_student 同事注冊(cè)對(duì) name 的監(jiān)聽
[self.xk_student addObserver:self forKeyPath:@"name"
options:(NSKeyValueObservingOptionNew) context:XKStudentContext];
[self.xku_student addObserver:self forKeyPath:@"name"
options:(NSKeyValueObservingOptionNew) context:XKUniversityStudentContext];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if(context == XKStudentContext) {
NSLog(@"\n change %@ \n XKStudentContext",change);
}else if (context == XKUniversityStudentContext){
NSLog(@"\n change %@ \n XKUniversityStudentContext",change);
}
}
- 給 name 賦值
self.xk_student.name = @"emiya";
self.xku_student.name = @"un emiya";
- 打印輸出結(jié)果
- 小結(jié):
Context
就是一個(gè)標(biāo)識(shí),為了就是當(dāng)key path
相同時(shí)用來區(qū)分子類
和分類
監(jiān)聽同一個(gè)屬性
automatic for Observers
- 我們可以設(shè)置
automaticallyNotifiesObserversForKey
返回NO
@implementation XKStudent
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
return NO;
}
- 設(shè)置
No
以后對(duì)屬性的監(jiān)聽就失效了积蜻,你也可以用key
進(jìn)行判斷闯割,對(duì)某一個(gè)屬性進(jìn)行設(shè)置。
mutableArray for Observe
- 我們?cè)僭囈幌氯绾螌?duì)可變數(shù)組對(duì)象進(jìn)行監(jiān)聽
// 可變數(shù)組觀察竿拆,先觀察再變值
self.xk_student.dataArray = [NSMutableArray array];
[self.xk_student addObserver:self forKeyPath:@"dataArray"
options:(NSKeyValueObservingOptionNew) context:NULL];
[[self.xk_student mutableArrayValueForKey:@"dataArray"] addObject:@"數(shù)組元素1"];
- 打印輸出
- 這里有個(gè)坑點(diǎn)就是必須調(diào)用
mutableArrayValueForKey:@"dataArray"]
宙拉,不能使用self.xk_student.dataArray
KVO 底層原理
- KVO 底層實(shí)現(xiàn)詳情
根據(jù)官方文檔 ??
KVO
的實(shí)現(xiàn)使用了一個(gè)叫做isa-swizzing
的技術(shù)。而isa-swizzing
可以理解為isa 指向的替換
(如不清楚isa
的可以看下這篇文章)isa
指針指向一個(gè)維護(hù)分派表的對(duì)象類丙笋。這個(gè)分派表本質(zhì)上包含指向類實(shí)現(xiàn)的方法的指針以及其他數(shù)據(jù)谢澈。- 當(dāng)一個(gè)觀察者為一個(gè)對(duì)象的屬性注冊(cè)時(shí),被觀察對(duì)象的
isa
指針被修改御板,指向一個(gè)中間類而不是真正的類锥忿。因此,isa
指針的值不一定反映實(shí)例的實(shí)際類怠肋。- 你決不能依賴
isa
指針來確定類的成員敬鬓。相反,你應(yīng)該使用class
方法來確定對(duì)象實(shí)例的類笙各。
- 說真心話技術(shù)這種官方文檔翻譯過來真心好難理解钉答,因此還是需要用
技術(shù)的方式
去驗(yàn)證。 - 進(jìn)入
lldb
進(jìn)行調(diào)試
- 由輸出可知 當(dāng) 進(jìn)行了
addObserver
后isa
->NSKVONotifying_XKStudent
這個(gè)
NSKVONotifying_XKStudent
到底是什么呢杈抢?NSKVONotifying_XKStudent
與XKStudent
有何關(guān)系呢数尿?
- 繼續(xù)
lldb
進(jìn)行調(diào)試
- 由??可知
XKStudent
與NSKVONotifying_XKStudent
是父子關(guān)系 - 繼續(xù)調(diào)試分析
NSKVONotifying_XKStudent
里面到底有什么呢? -
lldb
分析??
- 根據(jù)輸出結(jié)果可知
NSKVONotifying_XKStudent
有4
個(gè)Method
setName:
class
dealloc
_isKOVA
那這
4
個(gè)Method
里面到底做了什么的惶楼?
_isKOVA
其實(shí)就是一個(gè)標(biāo)識(shí)砌创。判斷是不是KVO
生成的虏缸。-
class
進(jìn)行分析,輸出??
你是否有疑問 isa
-> NSKVONotifying_XKStudent
,為何不輸出 NSKVONotifying_XKStudent
嫩实。 其實(shí) class
已經(jīng)做了處理,對(duì)外部讓我們開發(fā)人員看到的永遠(yuǎn)都是 XKStudent
窥岩〖紫祝可以簡單理解為:做了一次掩飾
。
-
dealloc
字面理解就是銷毀颂翼。
當(dāng)我們?cè)?addObserver
的時(shí)isa
->NSKVONotifying_XKStudent
晃洒。那何時(shí)把isa
指回去呢?
對(duì)dealloc
進(jìn)行分析朦乏,輸出??
由輸出可知球及, 當(dāng) removeObserver
的時(shí) , isa
-> XKStudent
呻疹,故能推導(dǎo)出其實(shí)在 dealloc
時(shí)候又把 isa
指回了 XKStudent
-
setName:
從字面理解就是setter
方法
疑問1:假設(shè)
KVO
對(duì)setter
可以進(jìn)行監(jiān)聽觀察吃引,那它是否也會(huì)對(duì)成員變量
進(jìn)行觀察呢?
開始進(jìn)行驗(yàn)證
// 添加成員變量
@interface XKStudent : NSObject{
@public
NSString *age;
}
// 添加觀察
[self.xk_student addObserver:self forKeyPath:@"name"
options:(NSKeyValueObservingOptionNew) context:NULL];
[self.xk_student addObserver:self forKeyPath:@"age"
options:(NSKeyValueObservingOptionNew) context:NULL];
// 進(jìn)行賦值
self.xk_student.name = @"emiya";
self.xk_student->age = @"20";
// 監(jiān)聽
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"\n change %@",change);
}
打印輸出??
change {
kind = 1;
new = emiya;
}
從輸出結(jié)果可知:KVO
只會(huì)對(duì)屬性 setter
進(jìn)行監(jiān)聽刽锤,無法對(duì)成員變量
進(jìn)行監(jiān)聽镊尺。
疑問2:已知
isa -> NSKVONotifying_XKStudent
,那么setName:
肯定是NSKVONotifying_XKStudent
對(duì)其進(jìn)行了賦值操作并思。那如果isa
->XKStudent
那么name
是否已經(jīng)有值了庐氮?
開始進(jìn)行驗(yàn)證
由輸出可知:在 isa
-> XKStudent
后,name
已經(jīng)有值
了 宋彼。
疑問3:可以肯定是在
KVO_XKStudent
這一級(jí)進(jìn)行了name
的賦值弄砍,那在XKStudent
里的name
是何時(shí)進(jìn)行賦值的呢?
開始在 lldb
里進(jìn)行符號(hào)斷點(diǎn)
watchpoint set variable self->_xk_student->_name
輸出??
bt
打印堆棧
由??可知
-> 1.touche'sBegin
進(jìn)行 KVO_XKStudent.setName:
-> 2.Foundation:_NSSetObjectValueAndNotify
-> 3.Foundation:-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
->4.Foundation:-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
進(jìn)入 4.Foundation:-[NSObject(NSKeyValueObservingPrivate)
查看匯編
在最后 using block
把值返回
進(jìn)入 2.Foundation:_NSSetObjectValueAndNotify
查看匯編
_implicitObservationInfo
會(huì)發(fā)送一個(gè)通知 输涕,這個(gè)通知就會(huì)來到 NSKeyValueDidChange
最后來到 _observeValueForKeyPath:ofObject:
音婶。
- 小結(jié):解釋動(dòng)態(tài)子類
KVO_XKStudent setter
到底做了什么。
其實(shí)就是調(diào)用了父類Student setter
方法占贫,然后在valueDidChange
后發(fā)起一個(gè)通知說明值
已經(jīng)賦值完畢了桃熄,最后來到observeValueForKeyPath
。