KVO實(shí)現(xiàn)原理

KVO

  1. KVO 是 Key-Value-Observing 的簡(jiǎn)稱董虱。

  2. KVO 是一個(gè)觀察者模式申鱼。觀察一個(gè)對(duì)象的屬性,注冊(cè)一個(gè)指定的路徑淫半,若這個(gè)對(duì)象的的屬性被修改匣砖,則 KVO 會(huì)自動(dòng)通知觀察者。

  3. 更通俗的話來說就是任何對(duì)象都允許觀察其他對(duì)象的屬性对人,并且可以接收其他對(duì)象狀態(tài)變化的通知规伐。

KVO 基本使用

1.// 注冊(cè)觀察者匣缘,實(shí)施監(jiān)聽;
[self.person addObserver:self
              forKeyPath:@"age"
                 options:NSKeyValueObservingOptionNew
                 context:nil];

2.// 回調(diào)方法培慌,在這里處理屬性發(fā)生的變化柑爸;
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context

3.// 移除觀察者;
[self removeObserver:self forKeyPath:@“age"];

KVO 在 Apple 中的 API 文檔如下:

Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class …

Apple 使用了 isa 攪拌技術(shù)(isa-swizzling)來實(shí)現(xiàn)的 KVO 馅而。當(dāng)一個(gè)觀察者注冊(cè)對(duì)象的一個(gè)屬性 isa 觀察對(duì)象的指針被修改瓮恭,指著一個(gè)中間類而不是在真正的類厘熟。

isa 指針的作用:每個(gè)對(duì)象都有 isa 指針维哈,指向該對(duì)象的類阔挠,它告訴 runtime 系統(tǒng)這個(gè)對(duì)象的類是什么脑蠕。

注:如果對(duì) runtime 不很清楚的話可以看下這篇文章Objective-C 中的 Runtime

舉個(gè)栗子:

_person = [[Person alloc] init];

/**
 *  添加觀察者
 *
 *  @param observer 觀察者
 *  @param keyPath  被觀察的屬性名稱
 *  @param options  觀察屬性的新值谴仙、舊值等的一些配置(枚舉值,可以根據(jù)需要設(shè)置锁摔,例如這里可以使用兩項(xiàng))
 *  @param context  上下文哼审,可以為nil。
 */
[_person addObserver:self
          forKeyPath:@"age"
             options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
             context:nil];
/**
 *  KVO回調(diào)方法
 *
 *  @param keyPath 被修改的屬性
 *  @param object  被修改的屬性所屬對(duì)象
 *  @param change  屬性改變情況(新舊值)
 *  @param context context傳過來的值
 */
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context
{
    NSLog(@"%@對(duì)象的%@屬性改變了:%@",object,keyPath,change);
 }
/**
 *  移除觀察者
 */
- (void)dealloc
{
    [self.person removeObserver:self forKeyPath:@"age"];
}

KVO 實(shí)現(xiàn)原理

當(dāng)某個(gè)類的對(duì)象第一次被觀察時(shí)十气,系統(tǒng)就會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類砸西,在這個(gè)派生類中重寫基類中任何被觀察屬性的 setter 方法址儒。 派生類在被重寫的 setter 方法實(shí)現(xiàn)真正的通知機(jī)制,就如前面手動(dòng)實(shí)現(xiàn)鍵值觀察那樣鸳慈。這么做是基于設(shè)置屬性會(huì)調(diào)用 setter 方法喧伞,而通過重寫就獲得了 KVO 需要的通知機(jī)制。當(dāng)然前提是要通過遵循 KVO 的屬性設(shè)置方式來變更屬性值翁逞,如果僅是直接修改屬性對(duì)應(yīng)的成員變量挖函,是無法實(shí)現(xiàn) KVO 的彼念。 同時(shí)派生類還重寫了 class 方法以“欺騙”外部調(diào)用者它就是起初的那個(gè)類。然后系統(tǒng)將這個(gè)對(duì)象的 isa 指針指向這個(gè)新誕生的派生類哲思,因此這個(gè)對(duì)象就成為該派生類的對(duì)象了吩案,因而在該對(duì)象上對(duì) setter 的調(diào)用就會(huì)調(diào)用重寫的 setter,從而激活鍵值通知機(jī)制靠益。此外残揉,派生類還重寫了 dealloc 方法來釋放資源。

派生類 NSKVONotifying_Person 剖析:

在這個(gè)過程壳快,被觀察對(duì)象的 isa 指針從指向原來的 Person 類镇草,被 KVO 機(jī)制修改為指向系統(tǒng)新創(chuàng)建的子類 NSKVONotifying_Person 類,來實(shí)現(xiàn)當(dāng)前類屬性值改變的監(jiān)聽竖伯。

所以當(dāng)我們從應(yīng)用層面上看來因宇,完全沒有意識(shí)到有新的類出現(xiàn),這是系統(tǒng)“隱瞞”了對(duì) KVO 的底層實(shí)現(xiàn)過程本姥,讓我們誤以為還是原來的類杭棵。但是此時(shí)如果我們創(chuàng)建一個(gè)新的名為 NSKVONotifying_Person 的類(),就會(huì)發(fā)現(xiàn)系統(tǒng)運(yùn)行到注冊(cè) KVO 的那段代碼時(shí)程序就崩潰先舷,因?yàn)橄到y(tǒng)在注冊(cè)監(jiān)聽的時(shí)候動(dòng)態(tài)創(chuàng)建了名為 NSKVONotifying_Person 的中間類滓侍,并指向這個(gè)中間類了互躬。

因而在該對(duì)象上對(duì) setter 的調(diào)用就會(huì)調(diào)用已重寫的 setter谣蠢,從而激活鍵值通知機(jī)制。這也是 KVO 回調(diào)機(jī)制裂逐,為什么都俗稱 KVO 技術(shù)為黑魔法的原因之一吧:內(nèi)部神秘泣栈、外觀簡(jiǎn)潔。

子類 setter 方法剖析:

KVO 在調(diào)用存取方法之前總是調(diào)用 willChangeValueForKey:掺涛,通知系統(tǒng)該 keyPath 的屬性值即將變更疼进。 當(dāng)改變發(fā)生后,didChangeValueForKey: 被調(diào)用矮燎,通知系統(tǒng)該 keyPath 的屬性值已經(jīng)變更赔癌。 之后,observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用峡谊。

重寫觀察屬性的 setter 方法這種方式是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的既们。 KVO 為子類的觀察者屬性重寫調(diào)用存取方法的工作原理在代碼中相當(dāng)于:

- (void)setName:(NSString *)newName
{
    [self willChangeValueForKey:@"name"];    // KVO在調(diào)用存取方法之前總調(diào)用
    [super setValue:newName forKey:@"name"]; // 調(diào)用父類的存取方法
    [self didChangeValueForKey:@"name"];     // KVO在調(diào)用存取方法之后總調(diào)用
}

總結(jié): KVO 的本質(zhì)就是監(jiān)聽對(duì)象的屬性進(jìn)行賦值的時(shí)候有沒有調(diào)用 setter 方法

  1. 系統(tǒng)會(huì)動(dòng)態(tài)創(chuàng)建一個(gè)繼承于 Person 的 NSKVONotifying_Person
  2. person 的 isa 指針指向的類 Person 變成 NSKVONotifying_Person啥纸,所以接下來的 person.age = newAge 的時(shí)候婴氮,他調(diào)用的不是 Person 的 setter 方法,而是 NSKVONotifying_Person(子類)的 setter 方法
  3. 重寫NSKVONotifying_Person的setter方法:[super setName:newName]
  4. 通知觀察者告訴屬性改變荣暮。

KVO 應(yīng)用

監(jiān)聽 ScrollView 的 contentOffSet 屬性罩驻,采取相應(yīng)的措施:

[scrollview addObserver:self
             forKeyPath:@“contentOffset                   
                options:NSKeyValueObservingOptionNew
                context:nil];

下面是用 KVO 寫的一個(gè)通過監(jiān)聽 scrollview 的 contentOffSet 實(shí)現(xiàn)的一個(gè)小刷新功能,感興趣的可以看下骏啰。

gif

KVO 總結(jié)

KVO 是一個(gè)對(duì)象能觀察另一個(gè)對(duì)象屬性的值抽高,KVO 適合任何對(duì)象監(jiān)聽另一個(gè)對(duì)象的改變,這是一個(gè)對(duì)象與另外一個(gè)對(duì)象保持同步的一種方法祈秕。KVO 只能對(duì)屬性做出反應(yīng)雏胃,不會(huì)用來對(duì)方法或者動(dòng)作做出反應(yīng)志鞍。

優(yōu)點(diǎn):

  1. 提供一個(gè)簡(jiǎn)單的方法來實(shí)現(xiàn)兩個(gè)對(duì)象的同步。
  2. 能夠提供觀察的屬性的新值和舊值统翩。
  3. 每一次屬性值改變都是自動(dòng)發(fā)送通知厂汗,不需要開發(fā)者手動(dòng)實(shí)現(xiàn)。
  4. 用 keypath 來觀察屬性娶桦,因此也可以觀察嵌套對(duì)象汁汗。

缺點(diǎn):

  1. 觀察的屬性必須使用字符串來定義,因此編譯器不會(huì)出現(xiàn)警告和檢查
  2. 只能重寫回調(diào)方法來后去通知祈争,不能自定義 selector角寸。當(dāng)觀察多個(gè)對(duì)象的屬性時(shí)就要寫"if"語句,來判斷當(dāng)前的回調(diào)屬于哪個(gè)對(duì)象的屬性的回調(diào)墨吓。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帖烘,一起剝皮案震驚了整個(gè)濱河市橄杨,隨后出現(xiàn)的幾起案子照卦,更是在濱河造成了極大的恐慌役耕,老刑警劉巖聪廉,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異框全,居然都是意外死亡干签,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門喘沿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚜印,“玉大人留量,你說我怎么就攤上這事∏蘖瑁” “怎么了孝赫?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)伐债。 經(jīng)常有香客問我致开,道長(zhǎng),這世上最難降的妖魔是什么虹蒋? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任魄衅,我火速辦了婚禮,結(jié)果婚禮上晃虫,老公的妹妹穿的比我還像新娘。我一直安慰自己扛吞,他們只是感情好荆责,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布草巡。 她就那樣靜靜地躺著型酥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪弥喉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天棚亩,我揣著相機(jī)與錄音讥蟆,去河邊找鬼。 笑死瘸彤,一個(gè)胖子當(dāng)著我的面吹牛笛钝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播结榄,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼囤捻,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了视哑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤枝秤,失蹤者是張志新(化名)和其女友劉穎慷嗜,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體薇溃,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沐序,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年堕绩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片特姐。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唐含,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捷枯,到底是詐尸還是另有隱情专执,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布他炊,位于F島的核電站,受9級(jí)特大地震影響蚕苇,放射性物質(zhì)發(fā)生泄漏凿叠。R本人自食惡果不足惜嚼吞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一舱禽、第九天 我趴在偏房一處隱蔽的房頂上張望恩沽。 院中可真熱鬧,春花似錦罗心、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至肩碟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腾务,已是汗流浹背削饵。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工未巫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人劈伴。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓握爷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親追城。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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