iOS--KVO的實現原理與具體應用

本文分爲2個部分:概念應用html

概念部分旨在剖析KVO這一設計模式的實現原理,應用部分經過建立的項目,以說明KVO技術在iOS開發中所帶來的做用;git

若是是做爲是剛接觸KVO的初學者,能夠在瞭解基本原理後粗略看幾遍底層實現原理,再認真閱讀第二部分的應用內容「學會」怎麼去使用KVO,日後再慢慢深刻了解KVO這一「黑魔法」技術的實現原理。github

本次開發環境: Xcode:7.2     iOS Simulator:iphone6   By:啊左         本文Demo下載連接:KVO演示Demo
設計模式

 

---------------------------------------------------------概念---------------------------------------------------------架構

1、KVO是什麼? iphone

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

在MVC設計架構下的項目,KVO機制很適合實現mode模型和view視圖之間的通信。post

例如:代碼中,在模型類A建立屬性數據,在控制器中建立觀察者,一旦屬性數據發生改變就收到觀察者收到通知,經過KVO再在控制器使用回調方法處理實現視圖B的更新;(本文中的應用就是這樣的例子.)編碼

 

2、實現原理?atom

KVO在Apple中的API文檔以下: url

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【可參考:Runtime的幾個小例子】 ,從以上Apple 的文檔能夠看出蘋果對於KVO機制的實現是一筆帶過,而具體的細節沒有過多的描述,可是咱們能夠經過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-swizzlingisa: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在調用存取方法以後總調用
}

 

3、特色:

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

因此使用KVO機制的前提是遵循 KVO 的屬性設置方式來變動屬性值。

 

4、步驟

  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{
}
複製代碼

 

6、上代碼~:

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
{
    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"]);
    }
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    
    /* 3.移除KVO */
    [self 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

 

7、拓展-->

 1.與KVC的不一樣?

  • 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此處零開銷觀察的優點。

相關文章
相關標籤/搜索