KVO 、category實現原理

一、KVO原理

1. KVO 簡介

KVO 是 Objective-C 對觀察者設計模式的一種實現。KVO 提供一種機制,指定一個被觀察對象,當對象某個屬性發生改變時,對象會獲得通知,并做出相應處理;

在 MVC 設計架構的項目,KVO機制很適合實現 model 模型和 View 視圖之間的通訊。

2. 實現原理

KVO 的實現依賴于 Objective-C 強大的 Runtime。
當觀察某對象α時,KVO 機制動態創建一個對象α當前類的子類,并為這個新的子類重寫被觀察屬性 keyPath 的 setter 方法。setter 方法隨后負責通知觀察對象屬性的改變狀況。

3. 深入剖析:

Apple 使用了 isa-swizzling 來實現 KVO。當觀察對象α時,KVO 機制動態創建一個新的名為:NSKVONotifying_α的新類,該類繼承自對象α的本類,且 KVO 為 NSKVONotifying_α重寫觀察屬性的 setter 方法,setter 方法會負責在調用原setter 方法之前和之后,通知所有觀察對象屬性值的更改情況。

NSKVONotifying_α類剖析:在這個過程,被觀察對象的 isa 指針從原來的α類,被 KVO 機制修改為指向系統新創建的子類 NSKVONotifying_α類,來實現當前類屬性值改變的監聽。

所以當我們從應用層面上看來,完全沒有意識到有新的類出現,這是系統對 KVO 的底層實現過程,此時如果我們創建一個新的名為“NSKVONotifying_α”的類,就會發現系統運行到注冊 KVO 的那段代碼時程序就會崩潰。因為系統在注冊監聽的時候創建了名為 NSKVONotifying_α的中間類,并指向這個中間類。

(isa 指針的作用: 每個對象都有 isa 指針,指向該對象的類,它告訴 Runtime 系統這個對象的類是什么。所以對象注冊為觀察者時,isa 指針指向新子類,那么這個被觀察的對象就神奇地變成新子類的對象(或實例)了。)因而在該對象上對 setter 的調用就會調用已重寫的 setter,從而激活鍵值通知機制。

子類 setter 方法剖析:KVO 的鍵值觀察通知依賴于 NSOjbect 的兩個方法:willChangeValueForKey:和 didChangeValueForKey:,在存取數值的前后分別調用2個方法:
被觀察屬性發生改變之前,willChangeValueForKey:被調用,通知系統該 keyPath 的屬性值即將變更;當改變發生后,didChangeValueForKey:被調用,通知系統該 keyPath 的屬性值已經變更;之后,observeValueForKey:ofObject:change:context:也會被調用。且重寫觀察屬性的 setter 方法這種繼承方式的注入是在運行時而不是編譯時實現的。

KVO 為子類的觀察者屬性重寫調用存取方法的工作原理在代碼中相當于:

-  (void)setName:(NSString *)newName{
    [self willChangeValueForKey:@"name"];
    [super setValue:newName forKey:@"name"];
    [self didiChangeValueForKey:@"name"];
}

4.特點:

觀察者觀察的是屬性,只有遵循 KVO 變更屬性值的方式才會執行 KVO 的回調方法,例如是否執行了 setter 方法、或者是否使用了 KVC 賦值。如果賦值沒有通過 setter 方法或者 KVC,而是直接修改屬性對應的成員變量,例如:僅調用_name = @"newName",這事是不會觸發 KVO 機制,更加不會調用回調方法的。所以使用 KVO 機制的前提是遵循 KVO 的屬性設置方式來變更屬性值。

5.步驟

  • 1.注冊觀察者,實施監聽;
  • 2.在回調方法中處理屬性發生的變化;
  • 3.移除觀察者

6. 實現方法

A. 注冊觀察者:

//第一個參數 observer:觀察者(這里觀察 self.myKVO 對象的屬性變化)
//第二個參數 keyPath: 被觀察的屬性名稱(這里 self.myKVO 中 num屬性值的改變)
//第三個參數 options:觀察屬性的新值、舊值等的一些配置(枚舉值,可以根據需要設置,例如這里可以使用兩項)
//第四個參數 context: 上下文,可以為 kvo 的回調方法傳值(例如設定為一個放置數據的字典)
[self.myKVO addObserver:self forKeyPath:@"num" options: NSKeyValueObservingOptionOld|NSKeyValueObservingOpitonNew context:nil];

B.屬性(keyPath)的值發生變化時,收到通知,調用以下方法:

//keyPath:屬性名稱
//object: 被觀察的對象
//change:變化前后的值都存儲在 change 字典中
//context: 注冊觀察者時,context 傳過來的值
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *) change context:(void *)context{
}

7.拓展

1.KVC 和 KVO 的不同
KVC(鍵值編碼),即 Key-Value Coding,一個非正式的 Protocol,使用字符串(鍵)訪問一個對象實例變量的機制。而不是通過調用 Setter、Getter 方法等顯式的存取方法去訪問。KVO(鍵值監聽),即 Key-Value Observing,它提供一種機制,當指定的對象的屬性被修改后,對象就會接受到通知,前提是執行了 setter 方法、或者使用了 KVC 賦值。

2.和 notification(通知)的區別?
notification 比 KVO 多了發送通知的一步。
兩者都是一對多,但是對象之間直接的交互,notification 明顯很多,需要 notificationCenter 來作為中間交互。而 KVO 如我們介紹的,設置觀察者->處理屬性變化,

notification 的優點是監聽不局限與屬性的變化,還可以對多種多樣的狀態變化進行監聽,監聽范圍廣,例如鍵盤、前后臺等系統通知的使用也更更顯靈活方便。

  1. 與 delegate 的不同

和 delegate 一樣,KVO 和 NSNotification 的作用都是類與類之間的通信。但是與 delegate 不同的是:
這兩個都是負責發送接收通知,剩下的事情由系統處理,所以不同返回值;而 delegate 則需要通信的對象通過變量(代理)聯系;
delegate 一般是一對一,而這兩個可以一對多。

二、Category實現原理

1. 簡介

category 是 Objective-C 2.0 之后添加的語言特性。主要作用是為已經存在的類添加方法。還有以下兩種用法:

可以把類的實現分開在幾個不同的文件里面。1)可以減少單個文件的體積, 2)可以把不同的功能組織到不同的 category 里 3)可以由多個開發者共同完成一個類 4)可以按需加載想要的 category 等等

聲明私有方法。

2. 比較 category 和 extension

extension 在編譯期決定,它是類的一部分,在編譯期和頭文件里的@interface 以及實現文件里的@implement 一起形成一個完整的類,它伴隨類的產生而產生,消亡而消亡。extension 一般用來隱藏類的私有信息,你必須有一個類的源碼才能為一個類添加 extension,所以無法為系統的類添加 extension。

但是 category 實在運行期決定的,就 category 和 extension 的區別來看,extension 可以添加實例變量,而 category 無法添加實例變量(因為在運行期,對象的內存布局已經確定,如果添加實例變量就會破壞類的內部布局,這對編譯型語言來說是災難性的)。

3. category 原理

所有 OC 類和對象,在 Runtime 層都是用 struct 表示的,category 也不例外,在 runtime 層,category 用結構體 category_t (在 objc-runtime-new.h 中可以找到此定義),它包括了:
1)類的名字(name)
2)類(cls)
3)category 中所有給類添加的實例方法的列表(instanceMethods)

  1. category 中所有添加的類方法的列表(classMethods)
    5) category 實現的所有協議的列表(Protocols)
    6) category 中添加的所有屬性(instanceProperties)
typedef  struct category_t {
        const  char  * name;
        classref_t  cls;
        struct  method_list_t  *instanceMethods;
        struct  method_list_t  * classMethods;
        struct  protocol_list_t  * protocols;
        struct  property_list_t  * instanceProperties;
} category_t;

從 category 的定義也可以看出 category 的可為(可以添加實例方法,類方法,甚至可以實現協議,添加屬性)和不可為(無法添加實例變量)。
MyClass.h:

#import  <Foundation/Foundation.h>
@interface MyClass:NSObject
- (void)printName;
@end

@interface MyClass(MyAddition)
@property(nonatomic,copy) NSString *name;
- (void)printName;
@end

MyClass.m

#import"MyClass.h"
@implementation MyClass
- (void)printName{
    NSLog(@"%@",@"MyClass");
}
@end

@implementation MyClass(MyAddition)
- (void)printName{
    NSLog(@"%@",@"MyAddition");
}

4. category 和關聯對象

在 category 里是無法為 category 添加實例變量的。但是我們很多時候需要在 category 中添加和對象關聯的值,這個時候可以求助關聯對象來實現。

MyClass + Category.h:

#import  “MyClass.h”
@interface MyClass(Category1)
@property(nonatomic,copy) NSString *name;
@end

MyClass+Category.m:

#import "MyClass+Category.h"
#import <objc/runtime.h>

@implementation MyClass(Category)

- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_COPY);
}

-(NSString *)name{

    NSString *nameObject = objc_getAssociatedObject(self, "name");
    return nameObject;

}


@end 
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容