1. 程式人生 > >iOS KVO詳解

iOS KVO詳解

-i iba app setter 通知 編碼 true 對比 idt

一、KVO 是什麽?

KVO 是 Objective-C 對觀察者設計模式的一種實現。【另外一種是:通知機制(notification),詳情參考:iOS 趣談設計模式——通知】;
KVO 提供一種機制,指定一個被觀察對象(例如 A 類),當對象某個屬性(例如 A 中的字符串 name)發生更改時,對象會獲得通知,並作出相應處理;【且不需要給被觀察的對象添加任何額外代碼,就能使用 KVO 機制】

在 MVC 設計架構下的項目,KVO 機制很適合實現 mode 模型和 view 視圖之間的通訊。
例如:代碼中,在模型類A創建屬性數據,在控制器中創建觀察者,一旦屬性數據發生改變就收到觀察者收到通知,通過 KVO 再在控制器使用回調方法處理實現視圖 B 的更新;(本文中的應用就是這樣的例子.)

二、實現原理?

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 …

KVO 的實現依賴於 Objective-C 強大的 Runtime,從以上 Apple 的文檔可以看出蘋果對於 KVO 機制的實現是一筆帶過,而具體的細節沒有過多的描述,但是我們可以通過 Runtime 的所提供的方法去探索【可參考:Runtime的幾個小例子】,關於KVO 機制的底層實現原理。為此啊左從網上的一些關於 KVO 的資料總結了有關的內容:

基本的原理:

當觀察某對象 A 時,KVO 機制動態創建一個對象A當前類的子類,並為這個新的子類重寫了被觀察屬性 keyPath 的 setter 方法。setter 方法隨後負責通知觀察對象屬性的改變狀況。

深入剖析

Apple 使用了 isa 混寫(isa-swizzling)來實現 KVO 。當觀察對象A時,KVO機制動態創建一個新的名為:NSKVONotifying_A

的新類,該類繼承自對象A的本類,且 KVO 為 NSKVONotifying_A 重寫觀察屬性的 setter 方法,setter 方法會負責在調用原 setter 方法之前和之後,通知所有觀察對象屬性值的更改情況。
(備註: isa 混寫(isa-swizzling)isa:is a kind of ; swizzling:混合,攪合;)

①NSKVONotifying_A 類剖析:在這個過程,被觀察對象的 isa 指針從指向原來的 A 類,被 KVO 機制修改為指向系統新創建的子類 NSKVONotifying_A 類,來實現當前類屬性值改變的監聽
所以當我們從應用層面上看來,完全沒有意識到有新的類出現,這是系統“隱瞞”了對 KVO 的底層實現過程,讓我們誤以為還是原來的類。但是此時如果我們創建一個新的名為“NSKVONotifying_A”的類,就會發現系統運行到註冊 KVO 的那段代碼時程序就崩潰,因為系統在註冊監聽的時候動態創建了名為 NSKVONotifying_A 的中間類,並指向這個中間類了。
isa 指針的作用:每個對象都有 isa 指針,指向該對象的類,它告訴 Runtime 系統這個對象的類是什麽。所以對象註冊為觀察者時,isa 指針指向新子類,那麽這個被觀察的對象就神奇地變成新子類的對象(或實例)了。) 因而在該對象上對 setter 的調用就會調用已重寫的 setter,從而激活鍵值通知機制。
—>我猜,這也是 KVO 回調機制,為什麽都俗稱KVO技術為黑魔法的原因之一吧:內部神秘、外觀簡潔。
②子類setter方法剖析:KVO 的鍵值觀察通知依賴於 NSObject 的兩個方法:willChangeValueForKey:和 didChangevlueForKey:,在存取數值的前後分別調用 2 個方法:
被觀察屬性發生改變之前,willChangeValueForKey:被調用,通知系統該 keyPath 的屬性值即將變更;當改變發生後, didChangeValueForKey: 被調用,通知系統該 keyPath 的屬性值已經變更;之後, observeValueForKey:ofObject:change:context: 也會被調用。且重寫觀察屬性的 setter 方法這種繼承方式的註入是在運行時而不是編譯時實現的。
KVO 為子類的觀察者屬性重寫調用存取方法的工作原理在代碼中相當於:

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

三、特點:

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


[應用部分]

四、步驟

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

五.實現方法(蘋果 API 文檔中的方法):

A.註冊觀察者:

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

六、上代碼~:

1.新建項目

UI界面設計如下:
第一個是便簽,用於顯示 num 數值,關聯 ViewController 並命名為:label
第二個是按鈕,用於改變 num 的數值,關聯 ViewController 並命名為:changeNum

技術分享圖片

2.模型創建

【新建一個 File,選擇 Cocoa Touch Class,命名為“myKVO”,記得選擇Subclass of “NSObject”.】代碼如下:

(myKVO.h):

@interface myKVO : NSObject
@property (nonatomic,assign)int num; //屬性設置為int類型的
num@end

(myKVO.m):

#import "myKVO.h"
@implementation myKVO
@synthesize num;
@end

3.在 ViewController 中監聽並響應屬性改變。

(ViewController.h):

#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *label;//便簽 label
- (IBAction)changeNum:(UIButton *)sender;           //按鈕事件 
@end

(ViewController.m):

#import "ViewController.h"
#import "myKVO.h"
@interface ViewController ()
@property (nonatomic,strong)myKVO *myKVO;
@end

@implementation ViewController

- (void)viewDidLoad { 
[super viewDidLoad]; 

self.myKVO = [[myKVO alloc]init]; 

/*1.註冊對象myKVO為被觀察者: option中,
NSKeyValueObservingOptionOld 以字典的形式提供 “初始對象數據”; 
NSKeyValueObservingOptionNew 以字典的形式提供 “更新後新的數據”; */ 
[self.myKVO addObserver:self forKeyPath:@"num" options:
NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
}

/* 2.只要object的keyPath屬性發生變化,就會調用此回調方法,進行相應的處理:UI更新:*/
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 
change:(NSDictionary<NSString *,id> *)change context:(void *)context{

// 判斷是否為self.myKVO的屬性“num”:
if([keyPath isEqualToString:@"num"] && object == self.myKVO) { 
// 響應變化處理:UI更新(label文本改變) 
self.label.text = [NSString stringWithFormat:@"當前的num值為:%@",
[change valueForKey:@"new"]]; 

//change的使用:上文註冊時,枚舉為2個,因此可以提取change字典中的新、舊值的這兩個方法 
NSLog(@"\\noldnum:%@ newnum:%@",[change valueForKey:@"old"],
[change valueForKey:@"new"]); 
}
}

/*KVO以及通知的註銷,一般是在-(void)dealloc中編寫。
至於很多小夥伴問為什麽要在didReceiveMemoryWarning?因為這個例子是在書本上看到的,所以試著使用它的例子。
但小編還是推薦把註銷行為放在-(void)dealloc中。(嚴肅臉??)
*/
- (void)didReceiveMemoryWarning { 
[super didReceiveMemoryWarning]; 
/* 3.移除KVO */ 
[self.myKVO removeObserver:self forKeyPath:@"num" context:nil];
}

//按鈕事件
- (IBAction)changeNum:(UIButton *)sender { 
//按一次,使num的值+1 
self.myKVO.num = self.myKVO.num + 1;
}
@end

調試:便簽label初始化沒有數值,當每次點擊按鈕後,label記錄的num隨之增加,表明按鈕使屬性num增加的同時,KVO機制發送通知,並調用observeValueForKeyPath:方法使UI更新。(本文Demo下載鏈接:KVO演示Demo)

七、拓展-->

1.KVC 與 KVO 的不同?

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

2.和 notification(通知)的區別?

notification 比 KVO 多了發送通知的一步。
兩者都是一對多,但是對象之間直接的交互,notification 明顯得多,需要notificationCenter 來做為中間交互。而 KVO 如我們介紹的,設置觀察者->處理屬性變化,至於中間通知這一環,則隱秘多了,只留一句“交由系統通知”,具體的可參照以上實現過程的剖析。

notification 的優點是監聽不局限於屬性的變化,還可以對多種多樣的狀態變化進行監聽,監聽範圍廣,例如鍵盤、前後臺等系統通知的使用也更顯靈活方便。
(參照通知機制第五節系統通知名稱內容)

3.與 delegate 的不同?

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

4.涉及技術:

KVC/KVO 實現的根本是 Objective-C 的動態性和 Runtime ,以及訪問器方法的實現;

總結:

對比其他的回調方式,KVO 機制的運用的實現,更多的由系統支持,相比 notification、delegate 等更簡潔些,並且能夠提供觀察屬性的最新值以及原始值;但是相應的在創建子類、重寫方法等等方面的內存消耗是很巨大的。所以對於兩個類之間的通信,我們可以根據實際開發的環境采用不同的方法,使得開發的項目更加簡潔實用。


另外需要註意的是,由於這種繼承方式的註入是在運行時而不是編譯時實現的,如果給定的實例沒有觀察者,那麽 KVO 不會有任何開銷,因為此時根本就沒有 KVO 代碼存在。但是即使沒有觀察者,委托和 NSNotification 還是得工作,這也是KVO此處零開銷觀察的優勢。




轉自:https://www.jianshu.com/p/e59bb8f59302

iOS KVO詳解