前言
Delegate 和 Notification 和 KVO 是幾種常見的傳值模式。
在我們實際的開發中經常會用到。
在一些iOS的面試中也是經常被提及,也是衡量一個iOS水平的指標。
那麼,這種模式的應用場景是什麼?iOS-SDK內部是如何實現的? 它們之間的區別又是什麼?
這篇文章中,我會為大家娓娓道來。
Delegate (代理,委託,協議)
我總結的Delegate的特點如下:
- 1對1的傳值
- 支援正向與反向傳值 (引數,返回值)
- 可以用require和optional來修飾的
在delegate中,我們經常會看到 should 字眼, 我們以UITextFieldDelegate舉例:
通常這樣的情況,都會有個返回值。
這就是所謂接受者的態度。你可以在Controller裡通過實現UITextFieldDelegate來控制TextFild的Return鍵是否有響應。
在UITextFaild內部一定存在這樣的程式碼:
- (void)xxxxMethod
{
//看看delegate實現時的返回值
BOOL isOK = [_delegate textFieldShouldReturn:self];
if (isOK) {
} else {
}
}
複製程式碼
同時,在這個例子中,我們也看到了,正向與反向的傳值。
Controller通過實現UITextFieldDelegate方法,得到了引數UITextFaild,同時處理了一個BOO型別的返回值給UITextField。
同樣的例子還有很多, 比如我們常見的UITableView。
有些朋友看到DataSource懵了, 其實不管叫什麼都是Delegate.
再來看兩個核心的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
-
為什麼帶NSKVONotifying_字首?
避免重複生成(你可能監聽N個屬性)
-
如何動態生成類?
看Runtime!
運用場景
我舉幾個簡單的例子,供參考:
用過QQ,微信的對這張圖不陌生
這裡會實時更新最新的一條聊天記錄,包括內容,時間,未讀數等等。
我們假設這是一個MessageItem物件。
在Cell內部,監聽一下個MessageItem的content,time,unread等幾個屬性,發生變化時給cell的label控制賦值即可。
其它場景還有很多,比如新聞類的APP,在列表中,讀過的都是淺灰色的背景,可以監聽下unread屬性。
寫了KVO又不得不提KVC,後面我會專門寫一篇文章,專門講解KVO與KVC的知識。
還有一點,Runtime有空一定要多學習!