iOS 底層學(xué)習(xí)20

前言

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:contextAccount將會(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ù)蝶俱,您可以指定NULLkey 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)行了 addObserverisa -> NSKVONotifying_XKStudent

這個(gè) NSKVONotifying_XKStudent 到底是什么呢杈抢? NSKVONotifying_XKStudentXKStudent 有何關(guān)系呢数尿?

  • 繼續(xù)lldb 進(jìn)行調(diào)試
  • 由??可知 XKStudentNSKVONotifying_XKStudent父子關(guān)系
  • 繼續(xù)調(diào)試分析 NSKVONotifying_XKStudent 里面到底有什么呢?
  • lldb 分析??
  • 根據(jù)輸出結(jié)果可知 NSKVONotifying_XKStudent4個(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

KVO 底層原理流程總結(jié)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末型奥,一起剝皮案震驚了整個(gè)濱河市瞳收,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌厢汹,老刑警劉巖螟深,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異烫葬,居然都是意外死亡界弧,警方通過查閱死者的電腦和手機(jī)凡蜻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來垢箕,“玉大人划栓,你說我怎么就攤上這事√趸瘢” “怎么了忠荞?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長帅掘。 經(jīng)常有香客問我委煤,道長,這世上最難降的妖魔是什么修档? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任碧绞,我火速辦了婚禮,結(jié)果婚禮上吱窝,老公的妹妹穿的比我還像新娘讥邻。我一直安慰自己,他們只是感情好癣诱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布计维。 她就那樣靜靜地躺著,像睡著了一般撕予。 火紅的嫁衣襯著肌膚如雪鲫惶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天实抡,我揣著相機(jī)與錄音欠母,去河邊找鬼。 笑死吆寨,一個(gè)胖子當(dāng)著我的面吹牛赏淌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播啄清,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼六水,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了辣卒?” 一聲冷哼從身側(cè)響起掷贾,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荣茫,沒想到半個(gè)月后统翩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體艳吠,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡玩祟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了旨剥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浅缸,死狀恐怖轨帜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情疗杉,我是刑警寧澤阵谚,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站烟具,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏奠蹬。R本人自食惡果不足惜朝聋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望囤躁。 院中可真熱鬧冀痕,春花似錦、人聲如沸狸演。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宵距。三九已至腊尚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間满哪,已是汗流浹背婿斥。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哨鸭,地道東北人民宿。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像像鸡,于是被迫代替她去往敵國和親活鹰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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