iOS 收集swift iOS Developer

使用 Swift 的泛型和闭包实现观察者模式

2016-04-08  本文已影响953人  Cyandev

我们经常遇到这种情况,就是一个应用中有某些变量可能需要经常被改变,而很多其他模块都需要用到这个变量,我们希望在这个变量被改变时,使用者能够及时知晓并进行更新(执行例如 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 呢?也就是说当一个对象需要观察这个变量,这个对象就把 weakself但做 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
        }
    }
}

这里,每次添加新观察者时都清理一遍无效的观察者,这样就可以保证可观的内存使用。

上面这段代码十分稳定,大家可以放心地在项目中去使用😄

上一篇下一篇

猜你喜欢

热点阅读