深度解析 Delegate 和 Notification 和 KVO

hehe寶哥發表於2018-02-24

前言

Delegate 和 Notification 和 KVO 是幾種常見的傳值模式。

在我們實際的開發中經常會用到。

在一些iOS的面試中也是經常被提及,也是衡量一個iOS水平的指標。

那麼,這種模式的應用場景是什麼?iOS-SDK內部是如何實現的? 它們之間的區別又是什麼?

這篇文章中,我會為大家娓娓道來。

Delegate (代理,委託,協議)

我總結的Delegate的特點如下:

  • 1對1的傳值
  • 支援正向與反向傳值 (引數,返回值)
  • 可以用require和optional來修飾的

在delegate中,我們經常會看到 should 字眼, 我們以UITextFieldDelegate舉例:

深度解析 Delegate 和 Notification 和 KVO

通常這樣的情況,都會有個返回值。

這就是所謂接受者的態度。你可以在Controller裡通過實現UITextFieldDelegate來控制TextFild的Return鍵是否有響應。

在UITextFaild內部一定存在這樣的程式碼:

- (void)xxxxMethod
{
    //看看delegate實現時的返回值
    BOOL isOK = [_delegate textFieldShouldReturn:self];
    if (isOK) {
        
    } else {
        
    }
}
複製程式碼

同時,在這個例子中,我們也看到了,正向與反向的傳值。

Controller通過實現UITextFieldDelegate方法,得到了引數UITextFaild,同時處理了一個BOO型別的返回值給UITextField。


同樣的例子還有很多, 比如我們常見的UITableView。

深度解析 Delegate 和 Notification 和 KVO

有些朋友看到DataSource懵了, 其實不管叫什麼都是Delegate.

深度解析 Delegate 和 Notification 和 KVO

再來看兩個核心的delegate方法:

  • 你的controller告訴tableview有多少行
  • 你的controller告訴tableview繪製的Cell是什麼樣的

這些都是通過返回值實現的。

注意:

所以在用delegate時,我們在用delegate時,除了正常的通過引數傳值,還要靈活的去運用返回值。


Notification (通知,訊息)

我總結Notification有如下特點:

  • 1對N (多)
  • 不關心返回值,單向的傳值
  • NSNotificationCenter單例統一處理髮通知
  • 通過不同的唯一的通知標識名NotificationName區分不同通知
  • 被觀察者主動發出通知

使用Notification一定要謹慎,由於1對多的緣故,避免濫用,不好查問題。

其次就是在Controller銷燬時,一定要登出掉通知。

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
複製程式碼

KVO

  • 1對N (多)
  • 只能監聽物件的屬性變化 (比較侷限)
  • 只有通過getters和setters來改變值才會觸發KVO
  • 被觀察者不用新增任何程式碼(比如傳送通知),與被觀察者完全解耦
  • 註冊觀察者時,屬性名都是通過NSString來查詢,容易出錯
  • KVO的要求是物件必須能支援kvc機制 (NSObject的子類)
  • 單向傳值

KVO原理

驗證KVO的原理很複雜,網上文章很多,也可以自己寫demo去驗證,我這裡就簡單的說明:

首先,你要註冊一個觀察者,觀察一下tableview的偏移量:

//新增監聽者
[self.tableView addObserver: self forKeyPath: @"contentOffset" options: NSKeyValueObservingOptionNew context: nil];
複製程式碼

此時,編譯器會修改監聽物件(tableView)的isa指標,讓這個指標指向一個新生成的(官方文件提及的)中間類。

其實這個中間類,就是帶有NSKVONotifying_字首的tableView的子類。

然後,它要通過setter,getter等方法,檢測一下你註冊的這個屬性(contentOffset)是否存在有效?

不存在就直接拋異常崩潰了!

在重新實現setter方法的時候,有兩個重要的方法:willChangeValueForKey和didChangeValueForKey,分別在賦值前後進行呼叫。

此外,還要遍歷所有的回撥監聽者,然後通知這些監聽者。

這時作為觀察者,我們可以得到物件屬性的變化值

- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object    
                        change:(NSDictionary<NSKeyValueChangeKey,id> *)change 
                       context:(void *)context
{
    
}
複製程式碼

所有的監聽者通過動態繫結的方式將其儲存起來,但這樣也會產生強引用,所以要釋放

- (void)dealloc
{
    [self.tableView removeObserver: self forKeyPath: @"contentOffset"];
}
複製程式碼

這裡只是拋磚引玉,其實iOS-sdk中的實現遠不止這些,還要複雜的多,多瞭解底層的原理,學習好的思路,有助於我們寫出更優秀的程式碼。

FAQ

  1. 為什麼帶NSKVONotifying_字首?

    避免重複生成(你可能監聽N個屬性)

  2. 如何動態生成類?

    看Runtime!


運用場景

我舉幾個簡單的例子,供參考:

用過QQ,微信的對這張圖不陌生

深度解析 Delegate 和 Notification 和 KVO

這裡會實時更新最新的一條聊天記錄,包括內容,時間,未讀數等等。

我們假設這是一個MessageItem物件。

在Cell內部,監聽一下個MessageItem的content,time,unread等幾個屬性,發生變化時給cell的label控制賦值即可。


其它場景還有很多,比如新聞類的APP,在列表中,讀過的都是淺灰色的背景,可以監聽下unread屬性。


寫了KVO又不得不提KVC,後面我會專門寫一篇文章,專門講解KVO與KVC的知識。

還有一點,Runtime有空一定要多學習!

相關文章