Swift 實現觀察者模式

本文翻譯自:An Observable Pattern Implementation in Swifthtml

問題

在過去的幾天裏,我都在進行着 Gumroad's Small Product Lab 的挑戰,就是使用Swift語言來開發一個Mac 應用。這個應用包含一個簡單的 結構體 struct 類型 AppConfig, 表示應用中用戶能夠配置的選項。我所須要的就是建立一個ViewController 讓用戶能夠編輯這些配置,也就是在這裏我遇到了困難。git

在Objective-C 中一般的作法是 使用 Cocoa Bindings來實現。這是一個構建在KVO之上的好功能,讓你可以在Interface Builder 上自動地綁定你的UI元素到實例變量。github

可是Cocoa Bindings 只做用於 NSObject 的子類上,我也想過直接把AppConfig 類型改成 NSObject,但把AppConfig做爲一個 值類型是最佳的實踐,並且我也不想由於這個小功能導入Objective-C的運行時。swift

因爲我原本就是要用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,並實現了咱們的協議,那麼最終須要下面這樣作:app

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!

完美,如今開始寫......ide

實現

考慮到觀測對象有狀態/生命週期,我決定把它做爲類來對待:ui

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)
        }
    }
}

最後,咱們須要實現 subscribeunsubscribe 方法來添加和刪除咱們的觀察者:

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
    }
}

下載工程代碼

相關文章
相關標籤/搜索