Two-way binding. Interstellar FRP - Cocoa

A small experiment how to two-way bind a String property to a NSTextField.

References:

Interstellar

How to use Functional Reactive Programming without Black Magic - Slide num. 40

var observableKey: UInt8 = 0

extension NSTextField: NSTextFieldDelegate {

    // text is a readonly property (proxy). We create an observable string when text property is read for the first time and add the observable string as a custom property of the NSTextField.
    // To be able to receive the notification when text in text field changes we set self as the self delegate.
    // controlTextDidChange(:) is called when the text changes where we can update the observable string with the textField.stringValue
    public var text: Observable<String> {
        var observable: Observable<String>
        if let existing = objc_getAssociatedObject(self, &observableKey) as? Observable<String> {
            observable = existing
        } else {
            observable = Observable<String>()

            self.delegate = self
            objc_setAssociatedObject(self, &observableKey, observable, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
        return observable
    }

    // Update text (observable string) with textField.stringValue when text in text field changes
    override open func controlTextDidChange(_ notification: Notification) {
        guard let textField = notification.object as? NSTextField else {
            return
        }
        self.text.update(textField.stringValue)
    }
}

// Two-way binding between CustomStringConvertible and NSTextField
extension Observable where T: CustomStringConvertible {

    func bind(to textField: NSTextField)  {
        self.subscribe { stringConvertible in
            textField.stringValue = String(describing: stringConvertible)
        }
        textField.text.subscribe { string in
            guard let value = string as? T else {
                return
            }
            self.update(value)
        }
    }
}

// Usage:

class ViewController: NSViewController {

    @IBOutlet weak var textField: NSTextField!

    let text = Observable<String>()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.text.bind(to: self.textField)
    }
}

Download demo project here