Search code examples
iosswiftfunctional-programmingreactive-cocoarx-swift

Two way binding in RxSwift


I read the two way binding operator in sample code of RxSwift.

func <-> <T>(property: ControlProperty<T>, variable: Variable<T>) -> Disposable {
    let bindToUIDisposable = variable.asObservable()
        .bindTo(property)
    let bindToVariable = property
        .subscribe(onNext: { n in
            variable.value = n
        }, onCompleted:  {
            bindToUIDisposable.dispose()
        })

    return StableCompositeDisposable.create(bindToUIDisposable, bindToVariable)
}

When property changed, it will notify variable, and set the variable's value, while the variable's value is set, it will notify the property. I think it will lead to endless loop...


Solution

  • Thanks for raising the question, I spent some time digging around the ControlProperty implementation (note I've added a .debug() call to trace the values generated for control property).

    public struct ControlProperty<PropertyType> : ControlPropertyType {
        public typealias E = PropertyType
    
        let _values: Observable<PropertyType>
        let _valueSink: AnyObserver<PropertyType>
    
        public init<V: ObservableType, S: ObserverType where E == V.E, E == S.E>(values: V, valueSink: S) {
            _values = values.debug("Control property values").subscribeOn(ConcurrentMainScheduler.instance)
            _valueSink = valueSink.asObserver()
        }
    
        public func on(event: Event<E>) {
            switch event {
            case .Error(let error):
                bindingErrorToInterface(error)
            case .Next:
                _valueSink.on(event)
            case .Completed:
                _valueSink.on(event)
            }
        }
    }
    

    My test setup was as following, I've removed all views positioning here to make it shorter:

    import UIKit
    import RxSwift
    import RxCocoa
    class ViewController: UIViewController {
        let variable = Variable<Bool>(false);
        let bag = DisposeBag();
    
        override func loadView() {
            super.loadView()
    
            let aSwitch = UISwitch();
            view.addSubview(aSwitch)
    
            (aSwitch.rx_value <-> variable).addDisposableTo(bag);
    
            let button = UIButton();
            button.rx_tap.subscribeNext { [weak self] in
                self?.variable.value = true;
            }.addDisposableTo(bag)
            view.addSubview(button);
        }
     }
    
    infix operator <-> {
    }
    
    func <-> <T>(property: ControlProperty<T>, variable: Variable<T>) -> Disposable{
        let bindToUIDisposable = variable.asObservable().debug("Variable values in bind")
        .bindTo(property)
    
        let bindToVariable = property
            .debug("Property values in bind")
            .subscribe(onNext: { n in
                variable.value = n
                }, onCompleted:  {
                     bindToUIDisposable.dispose()
            })
    
        return StableCompositeDisposable.create(bindToUIDisposable, bindToVariable)
     }
    

    Now to the results. First we try tapping the button, which should set the variable to true. This triggers on(event: Event<E>) on ControlProperty and sets the switch value to true.

    2016-05-28 12:24:33.229: Variable values in bind -> Event Next(true)
    
    // value flow
    value assigned to Variable -> 
    Variable emits event -> 
    ControlProperty receives event -> 
    value assigned to underlying control property (e.g. `on` for `UISwitch`)
    

    Next lets trigger the switch itself. So as we can see, the control generated an event as a result of UIControlEventValueChanged which was passed through _values on ControlProperty, and then its value got assigned to Variable value as in example above. But there's no loop, since update to the Variable value doesn't trigger a control event on the switch.

    2016-05-28 12:29:01.957: Control property values -> Event Next(false)
    2016-05-28 12:29:01.957: Property values in bind -> Event Next(false)
    2016-05-28 12:29:01.958: Variable values in bind -> Event Next(false)
    
    // value flow
    trigger the state of control (e.g. `UISwitch`) -> 
    ControlProperty emits event -> 
    value assigned to Variable -> 
    Variable emits event -> 
    ControlProperty receives event -> 
    value assigned to underlying control property (e.g. `on` for `UISwitch`)
    

    So a simple explanation would be:

    • a value from a control is emitted once some kind of UIControlEvent is triggered
    • when a value is assigned directly to the control property, the control doesn't trigger a change event so there's no loop.

    Hope it helps, sorry for a bit messy explanation - I've found it out by experiment)