我們經(jīng)常遇到這種情況,就是一個應(yīng)用中有某些變量可能需要經(jīng)常被改變僧凤,而很多其他模塊都需要用到這個變量畜侦,我們希望在這個變量被改變時,使用者能夠及時知曉并進(jìn)行更新(執(zhí)行例如 UI 刷新躯保、重載等操作)旋膳。傳統(tǒng)一點的做法我們可能會去使用NSNotificationCenter
這個類,它確實非常實用途事,可以輕松做到類似 PubSub 的功能验懊,但是如果有許多變量,我們豈不是要聲明許多 Notification尸变?這顯然是低效且不現(xiàn)實义图。
本文將利用 Swift 的語言特效一步步打造一個十分好用的觀察者模式。
石器時代
使用泛型封裝變量召烂,然后給出一個閉包數(shù)組碱工,當(dāng)變量變化時遍歷閉包數(shù)組,把新值傳過去:
import Foundation
class UZObservable<T> {
var value: T? {
set(newValue) {
self._value = newValue
self.notify()
}
get {
return self._value
}
}
typealias Observer = T? -> Void
private var _value: T?
private var observers = [Observer]()
func notify() {
self.observers.forEach { $0(self.value) }
}
func bindObserver(observer: Observer) {
self.observers.append(observer)
}
}
很簡單奏夫,沒什么可解釋的怕篷。注意一點,閉包需要用 typealias 指派一個類型酗昼,不然編譯器不認(rèn)...
隨手?jǐn)]一個測試界面廊谓,我們來看看效果。
然后是使用代碼:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
message.bindObserver({ [weak self] (message: String?) in
self?.label.text = message
})
}
不出意外的麻削,我們初步實現(xiàn)了觀察者模式蒸痹。
鐵器時代
但是春弥,但是。隨著我們的項目越來越大电抚,一個變量的觀察者列表會越來越龐大惕稻,然而有許多觀察者實際已經(jīng)被釋放了,所以我們必須要有一個方法來處理冗余的觀察者蝙叛。于是俺祠,我們可以使用 Dictionary
,也就是一個鍵對應(yīng)一個觀察者閉包借帘,當(dāng)觀察者被釋放的時候把相應(yīng)的鍵移除掉蜘渣。
于是代碼變成這樣:
import Foundation
class UZObservable<T> {
var value: T? {
set(newValue) {
self._value = newValue
self.notify()
}
get {
return self._value
}
}
typealias Observer = T? -> Void
private var _value: T?
private var observers = [String:Observer]()
func notify() {
for (_, observer) in self.observers {
observer(self.value)
}
}
func bindObserver(observer: Observer, forKey key: String) {
self.observers[key] = observer
}
func removeObserverForKey(key: String) {
self.observers.removeValueForKey(key)
}
}
看起來也蠻簡單,但是問題又產(chǎn)生了...字符串化的 key 是十分容易碰撞的肺然,好了蔫缸,這個方法顯然也是不行的。
信息化時代
看來我們需要對觀察者進(jìn)行一番修改了际起,為何不用一個引用對象當(dāng)做 key 呢拾碌?也就是說當(dāng)一個對象需要觀察這個變量,這個對象就把 weak 的 self
但做 key 添加到觀察者列表中街望,然后我們定期檢測這個對象是否為nil
校翔,如果是就把它剔除掉。這顯然更加完美灾前,因為我們再不用絞盡腦汁地去想各種奇怪的 key 了防症。
實現(xiàn)如下:
import Foundation
class UZObserver<T> {
var closure: (T? -> Void)!
weak var reference: AnyObject?
}
class UZObservable<T> {
var value: T? {
set(newValue) {
self._value = newValue
self.notify()
}
get {
return self._value
}
}
private var _value: T?
private var observers = [UZObserver<T>]()
func notify() {
observers.forEach { if $0.reference != nil { $0.closure(self.value) } }
}
func bindObserver(observer: AnyObject, closure: T? -> Void) {
self.clean()
let _observer = UZObserver<T>()
_observer.closure = closure
_observer.reference = observer
self.observers.append(_observer)
}
func clean() {
self.observers = self.observers.filter {
return $0.reference != nil
}
}
}
這里,每次添加新觀察者時都清理一遍無效的觀察者哎甲,這樣就可以保證可觀的內(nèi)存使用蔫敲。
上面這段代碼十分穩(wěn)定,大家可以放心地在項目中去使用??