使用 Swift 的泛型和闭包实现观察者模式
我们经常遇到这种情况,就是一个应用中有某些变量可能需要经常被改变,而很多其他模块都需要用到这个变量,我们希望在这个变量被改变时,使用者能够及时知晓并进行更新(执行例如 UI 刷新、重载等操作)。传统一点的做法我们可能会去使用NSNotificationCenter
这个类,它确实非常实用,可以轻松做到类似 PubSub 的功能,但是如果有许多变量,我们岂不是要声明许多 Notification?这显然是低效且不现实。
本文将利用 Swift 的语言特效一步步打造一个十分好用的观察者模式。
石器时代
使用泛型封装变量,然后给出一个闭包数组,当变量变化时遍历闭包数组,把新值传过去:
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 指派一个类型,不然编译器不认...
随手撸一个测试界面,我们来看看效果。
Testing UI
然后是使用代码:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
message.bindObserver({ [weak self] (message: String?) in
self?.label.text = message
})
}
Figure 1.
Figure 2.
不出意外的,我们初步实现了观察者模式。
铁器时代
但是,但是。随着我们的项目越来越大,一个变量的观察者列表会越来越庞大,然而有许多观察者实际已经被释放了,所以我们必须要有一个方法来处理冗余的观察者。于是,我们可以使用 Dictionary
,也就是一个键对应一个观察者闭包,当观察者被释放的时候把相应的键移除掉。
于是代码变成这样:
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)
}
}
看起来也蛮简单,但是问题又产生了...字符串化的 key 是十分容易碰撞的,好了,这个方法显然也是不行的。
信息化时代
看来我们需要对观察者进行一番修改了,为何不用一个引用对象当做 key 呢?也就是说当一个对象需要观察这个变量,这个对象就把 weak 的 self
但做 key 添加到观察者列表中,然后我们定期检测这个对象是否为nil
,如果是就把它剔除掉。这显然更加完美,因为我们再不用绞尽脑汁地去想各种奇怪的 key 了。
实现如下:
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
}
}
}
这里,每次添加新观察者时都清理一遍无效的观察者,这样就可以保证可观的内存使用。
上面这段代码十分稳定,大家可以放心地在项目中去使用😄