MG--探究KVO的底層實現原理

neuyu發表於2021-09-09

willChangeValueForKey:color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:各個引數的作用分別是什麼, observercolor{#00DD00}{observer}observer中需要實現哪個方法才能獲得KVOcolor{#dd0000}{KVO}KVO回撥

/**
 *  1. self.person要監聽的物件
 *  2. 引數說明
 *  @param addObserver  觀察者負責處理監聽事件的物件
 *  @param forKeyPath 要監聽的屬性
 *  @param  options 觀察的選項觀察新、舊值也可以都觀察
 *  @param context 上下文用於傳遞資料可以利用上下文區分不同的監聽
 */
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];

/**
 *  當監控的某個屬性的值改變了就會呼叫
 *
 *  @param keyPath 監聽的屬性名
 *  @param object  屬性所屬的物件
 *  @param change  屬性的修改情況屬性原來的值`oldValue`、屬性最新的值`newValue`
 *  @param context 傳遞的上下文資料與監聽的時候傳遞的一致可以利用上下文區分不同的監聽
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"%@物件的%@屬性改變了%@", object, keyPath, change);
}

一、KVO(Key−ValueObserving)color{#dd0000}{KVO(Key-Value Observing)}KVO(KeyValueObserving)

KVOcolor{#dd0000}{KVO}KVO 是 Objective-C 對觀察者模式Observer Pattern的實現。也是 Cocoa Binding 的基礎。當被觀察物件的某個屬性發生更改時觀察者物件會獲得通知。

有意思的是你不需要給被觀察的物件新增任何額外程式碼就能使用 KVOcolor{#dd0000}{KVO}KVO 。這是怎麼做到的

二、 KVOcolor{#dd0000}{KVO}KVO內部實現原理

  • KVOcolor{#dd0000}{KVO}KVO是基於runtime機制實現的
  • 當某個類的屬性物件第一次被觀察時系統就會在執行期動態地建立該類的一個派生類在這個派生類中重寫基類中任何被觀察屬性的setter 方法。派生類在被重寫的setter方法內實現真正的通知機制
  • 如果原類為Person那麼生成的派生類名為NSKVONotifyingPersoncolor{#00DD00}{NSKVONotifying_Person}NSKVONotifyingPerson
  • 每個類物件中都有一個isa指標指向當前類當一個類物件的第一次被觀察那麼系統會偷偷將isa指標指向動態生成的派生類從而在給被監控屬性賦值時執行的是派生類的setter方法
  • 鍵值觀察通知依賴於NSObject 的兩個方法: willChangeValueForKey:color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:didChangeValueForKey:color{#00DD00}{didChangeValueForKey:}didChangeValueForKey:在一個被觀察屬性發生改變之前 willChangeValueForKey:color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:一定會被呼叫這就 會記錄舊的值。而當改變發生後didChangeValueForKey:color{#00DD00}{didChangeValueForKey:}didChangeValueForKey:會被呼叫繼而 observeValueForKey:ofObject:change:context:color{#00DD00}{observeValueForKey:ofObject:change:context:}observeValueForKey:ofObject:change:context:也會被呼叫。
  • 補充KVOcolor{#dd0000}{KVO}KVO的這套實現機制中蘋果還偷偷重寫了class方法讓我們誤認為還是使用的當前類從而達到隱藏生成的派生類
    圖片描述

三、如何手動觸發一個value的KVOcolor{#dd0000}{KVO}KVO

  • 自動觸發的場景在註冊KVO之前設定一個初始值註冊之後設定一個不一樣的值就可以觸發了
  • 想知道如何手動觸發必須知道自動觸發 KVO 的原理見上面的描述
  • 手動觸發演示
@property (nonatomic, strong) NSDate *now;

- (void)viewDidLoad
{
    [super viewDidLoad];

    // “手動觸發self.now的KVO”必寫。
    [self willChangeValueForKey:@"now"];

    // “手動觸發self.now的KVO”必寫。
    [self didChangeValueForKey:@"now"];
}

圖片描述


四、補充 如何關閉預設的KVOcolor{#dd0000}{KVO}KVO的預設實現並進入自定義的KVOcolor{#dd0000}{KVO}KVO實現看連結


五、附註: KVCcolor{#0000FF}{KVC}KVC底層實現原理(如下)

KVOcolor{#dd0000}{KVO}KVO的基礎是KVCcolor{#0000FF}{KVC}KVCKVCcolor{#0000FF}color{#0000FF}{KVC}KVC運用了一個isa-swizzling技術. isa-swizzling就是型別混合指標機制, 將2個物件的isa指標互相調換, 就是俗稱的黑魔法.
KVCcolor{#0000FF}{KVC}KVC主要透過isa-swizzling, 來實現其內部查詢定位的. 預設的實現方法由NSOject提供isa指標, 如其名稱所指,(就是is a kind of的意思), 指向分發表物件的類. 該分發表實際上包含了指向實現類中的方法的指標, 和其它資料。

  • 具體主要分為三大步
    第一步尋找該屬性有沒有setsetter方法有就直接賦值
    第二步尋找有沒有該屬性帶下劃線的成員屬性有就直接賦值
    第三步尋找有沒有該屬性的成員屬性有就直接賦值
  • 或者這麼說
    1、首先搜尋setKey:方法.(key指成員變數名, 首字母大寫)
    2、上面的setter方法沒找到, 如果類方法accessInstanceVariablesDirectly返回YES. 那麼按 _key, _isKeykey, iskey的順序搜尋成員名.(NSKeyValueCodingCatogery中實現的類方法, 預設實現為返回YES)
    3、如果沒有找到成員變數, 呼叫setValue:forUnderfinedKey:

比如說如下的一行KVC的程式碼

  • 舉個e.g:

	[object setValue:@"13123" forKey:@"uuid"];

	就會被編譯器處理成:
	// 首先找到對應sel
	SEL sel = sel_get_ uuid("setValue:forKey:");
	// 根據object->isa找到sel對應的IMP實現指標
	IMP method = objc_msg_lookup (object->isa,sel);
	// 呼叫指標完成KVC賦值
	method(object, sel, @"13123", @"uuid");

  • 可供參考文章


面試題

透過修改類的成員變數不會觸發KVOcolor{#dd0000}{KVO}KVO,那為什麼透過KVCcolor{#0000FF}{KVC}KVC的setValue:forkey: 給成員變數賦值會觸發KVO呢?
首先KVOcolor{#dd0000}{KVO}KVO的基礎是KVCcolor{#0000FF}{KVC}KVC我們看下KVCcolor{#0000FF}{KVC}KVC的底層實現
1、首先搜尋setKey:方法.(key指成員變數名, 首字母大寫)
2、上面的setter方法沒找到, 如果類方法accessInstanceVariablesDirectlycolor{#00DD00}{accessInstanceVariablesDirectly}accessInstanceVariablesDirectly返回YES. 那麼按 _key, _isKeykey, iskey的順序搜尋成員名。
如果找到了就會觸發KVO因為底層內部會呼叫willChangeValueForKey:color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:didChangeValueForKey:color{#00DD00}{didChangeValueForKey:}didChangeValueForKey:方法。
你可以重寫該類的呼叫willChangeValueForKey:color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:didChangeValueForKey:color{#00DD00}{didChangeValueForKey:}didChangeValueForKey:方法去驗證當KVC改變屬性值的時候比如Person繼承NSObject它有一個成員變數@public int _age;[self.person1 setValue:@(10) forKey:@“age”]; 會呼叫willChangeValueForKey:color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:didChangeValueForKey:color{#00DD00}{didChangeValueForKey:}didChangeValueForKey:方法。所以會觸發KVO。
透過修改類的成員變數不會觸發KVO因為成員變數不會生成setter方法直接訪問成員變數自然不會觸發KVO而要觸發KVO本質是必須呼叫呼叫willChangeValueForKey:color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:didChangeValueForKey:color{#00DD00}{didChangeValueForKey:}didChangeValueForKey:方法。KVO的底層實現也是透過重寫setter方法 setter方法裡面呼叫willChangeValueForKey:color{#00DD00}{willChangeValueForKey:}willChangeValueForKey:didChangeValueForKey:color{#00DD00}{didChangeValueForKey:}didChangeValueForKey:方法。
圖片描述


  • 輕輕點選關注我


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4550/viewspace-2822616/,如需轉載,請註明出處,否則將追究法律責任。

相關文章