Swift 实现观察者模式

本文翻译自:An Observable Pattern Implementation in Swift

问题

在过去的几天里,我都在进行着 Gumroad's Small Product Lab 的挑战,就是使用Swift语言来开发一个Mac 应用。这个应用包含一个简单的 结构体 struct 类型 AppConfig, 表示应用中用户可以配置的选项。我所需要的就是创建一个ViewController 让用户可以编辑这些配置,也就是在这里我遇到了困难。

在Objective-C 中通常的做法是 使用 Cocoa Bindings来实现。这是一个构建在KVO之上的好功能,让你能够在Interface Builder 上自动地绑定你的UI元素到实例变量。

但是Cocoa Bindings 只作用于 NSObject 的子类上,我也想过直接把AppConfig 类型改为 NSObject,但把AppConfig作为一个 值类型是最佳的实践,而且我也不想因为这个小功能导入Objective-C的运行时。

由于我本来就是要用Swift来进行挑战这个项目的,所以我决定自己实现一个Swift版本的观察者模式。

到处都是协议

首先我需要定义一个协议,封装了我的绑定操作:

protocol ObservableProtocol {
    typealias T
    var value: T { get set }
    func subscribe(observer: AnyObject,
               block: (newValue: T, oldValue: T) -> ())
    func unsubscribe(observer: AnyObject)
}

假设我们在一个对象类型的上下文中,这里是self,并实现了我们的协议,那么最终需要下面这样做:

let initial = 3
var v = initial
var obs = Observable(initial)

obs.subscribe(self) { (newValue, oldValue) in
    print("Object updated!")
    v = newValue
}

obs.value = 4  // Trigger update.
print(v)       // 4!

完美,现在开始写......

实现

考虑到观测对象有状态/生命周期,我决定把它作为类来对待:

public final class Observable<T>: ObservableProtocol {
    // ...
}

我们将开始定义一个变量以及其它方便使用的类型:

我们的subscribers 模型就是一个个的观察者,这是一个 ObserversEntry 元祖类型的数组,包括了一个监听对象和一个闭包,这个闭包会在观察的对象触发时执行。我们可以通过 unsubscribe 方法来查找添加的观察者,并移除特定的观察者。

typealias ObserverBlock = (newValue: T, oldValue: T) -> ()
typealias ObserversEntry = (observer: AnyObject, block: ObserverBlock)
private var observers: Array<ObserversEntry>

现在需要在我们的类中实现init方法,默认的构造器只需要一个简单的初始值,作为我们观察到的初始值。同时这个构造器也需要初始化我们的非可选的 observers 数组变量。

init(_ value: T) {
    self.value = value
    observers = []
}

同时我们还要实现 didSet 方法,来在这个观察值发生变化时通知我们的观察者:

var value: T {
    didSet {
        observers.forEach { (entry: ObserversEntry) in
            // oldValue is an implicit parameter to didSet in Swift!
            let (_, block) = entry
            block(newValue: value, oldValue: oldValue)
        }
    }
}

最后,我们需要实现 subscribe unsubscribe 方法来添加和删除我们的观察者:

func subscribe(observer: AnyObject, block: ObserverBlock) {
    let entry: ObserversEntry = (observer: observer, block: block)
    observers.append(entry)
}

func unsubscribe(observer: AnyObject) {
    let filtered = observers.filter { entry in
        let (owner, _) = entry
        return owner !== observer
    }

    observers = filtered
}

注意:上面的实现只是最简单的,并没有考虑其它的异常情况。

语法糖

虽然上面的已经可以正常工作了,但是我还是想通过一些语法糖来减少重复编写 foo.value = <value>,所以我决定重载<< 这个符号。

func <<<T>(observable: Observable<T>, value: T) {
    observable.value = value
}

更新: Chris Lattner ,Swift的发明者说了,建议我不要随意重载已经存在的操作符号,所以如果你使用这个代码,你可以重载 <~ 这个符号,或者其它相似的但是唯一的符号。

例子

/// A view controller supporting editing of the app's config.
class PreferencesViewController: NSViewController {
    // The model layer.
    var configuration: ApplicationConfiguration

    // The view and object supporting the "controller".
    @IBOutlet var portTextField: NSTextField!
    var port: Observable<Int>

    // ...

    // MARK: NSViewController
    override func viewDidLoad() {
        super.viewDidLoad()

        port.subscribe(self) { (port, _) in
            // Ignore the old value, but update config with the new.
            self.configuration.port = port

            // You can trigger anything from here! Save to disk, etc...
            // Keeps action/UI code clean.
        }

        // ...
        // Assume `portTextFieldDidUpdate` is wired to be
        // called when portTextField's value updates.
        // ...
    }

    // MARK: Helpers
    func portTextFieldDidUpdate(value: Int) {
        port << value
    }
}

下载工程代码

posted @ 2015-11-03 14:59  剑尖  阅读(788)  评论(0编辑  收藏  举报