Two way binding in RxSwift

匿名 (未验证) 提交于 2019-12-03 03:08:02

问题:

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

回答1:

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)



回答2:

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 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!