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...
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)
I believe you can just use bindTo
?. Here are implementations for ControlProperty <-> Variable
and Variable <-> Variable
:
infix operator <-> { precedence 130 associativity left } func <-><T: Comparable>(property: ControlProperty<T>, variable: Variable<T>) -> Disposable { let variableToProperty = variable.asObservable() .distinctUntilChanged() .bindTo(property) let propertyToVariable = property .distinctUntilChanged() .bindTo(variable) return StableCompositeDisposable.create(variableToProperty, propertyToVariable) } func <-><T: Comparable>(left: Variable<T>, right: Variable<T>) -> Disposable { let leftToRight = left.asObservable() .distinctUntilChanged() .bindTo(right) let rightToLeft = right.asObservable() .distinctUntilChanged() .bindTo(left) return StableCompositeDisposable.create(leftToRight, rightToLeft) }
Examples of ControlProperty <-> Variable
(such as UITextField
and UITextView
) are in the RxSwiftPlayer project
// Example of Variable <-> Variable let disposeBag = DisposeBag() let var1 = Variable(1) let var2 = Variable(2) (var1 <-> var2).addDisposableTo(disposeBag) var1.value = 10 print(var2.value) // 10 var2.value = 20 print(var1.value) // 20