简体   繁体   English

RxSwift 中的双向绑定

[英]Two way binding in RxSwift

I read the two way binding operator in sample code of RxSwift.我在 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.property改变时,它会通知变量,并设置变量的值,当变量的值被设置时,它会通知属性。 I think it will lead to endless loop...我认为这会导致无限循环......

I believe you can just use bindTo 🙂.我相信你可以使用bindTo 🙂。 Here are implementations for ControlProperty <-> Variable and Variable <-> Variable :以下是ControlProperty <-> VariableVariable <-> 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 ControlProperty <-> Variable (例如UITextFieldUITextView )的示例RxSwiftPlayer 项目中

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

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).感谢您提出这个问题,我花了一些时间研究ControlProperty实现(注意我添加了一个.debug()调用来跟踪为控件属性生成的值)。

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 .首先我们尝试点击按钮,这应该将变量设置为true This triggers on(event: Event<E>) on ControlProperty and sets the switch value to true .这会on(event: Event<E>) ControlProperty on(event: Event<E>)触发on(event: Event<E>)并将开关值设置为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.正如我们所看到的,控件生成了一个事件作为UIControlEventValueChanged的结果,该事件通过 ControlProperty 上的_values传递,然后它的值被分配给Variable值,如上例所示。 But there's no loop, since update to the Variable value doesn't trigger a control event on the switch.但是没有循环,因为对Variable值的更新不会触发开关上的控制事件。

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一旦某种UIControlEvent被触发,就会发出来自控件的值
  • 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)希望它有所帮助,抱歉解释有点乱——我是通过实验发现的)

You type anything it will be clear after 5 seconds.您输入任何内容,5 秒后就会清楚。 This was taken from above answer这是从上面的答案中获取的

import UIKit
import RxSwift
import RxCocoa

class UserViewModel {
    let username = BehaviorSubject<String?>(value: "")
}

class ViewController: UIViewController {

    @IBOutlet weak var email: UITextField!

    var userViewModel = UserViewModel()
    let bag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        userViewModel.username.asObservable().subscribe { print($0) }.disposed(by: bag)
        (email.rx.text <-> userViewModel.username).disposed(by: bag)

        // clear value of the username.
        DispatchQueue.main.asyncAfter(deadline: .now()+5) {
            self.userViewModel.username.onNext(nil)
        }
    }
}

infix operator <->

@discardableResult func <-><T>(property: ControlProperty<T>, variable: BehaviorSubject<T>) -> Disposable {
    let variableToProperty = variable.asObservable()
        .bind(to: property)

    let propertyToVariable = property
        .subscribe(
            onNext: { variable.onNext($0) },
            onCompleted: { variableToProperty.dispose() }
    )

    return Disposables.create(variableToProperty, propertyToVariable)
}

There is no obstacle to bind BehaviourRelay back to control property.BehaviourRelay绑定回控制属性没有任何障碍。 You just need to filter events with the same value (to prevent infinite loop).您只需要过滤具有相同值的事件(以防止无限循环)。

For example, in my case, I need to bind email to text field.例如,就我而言,我需要将电子邮件绑定到文本字段。 But I want to remove whitespaces during email input.但我想在电子邮件输入期间删除空格。 Here is an example how I achieved it:这是我如何实现它的示例:

emailTextField.rx.text
  .map { $0?.trimmingCharacters(in: CharacterSet.whitespaces) } // remove whitespaces from input
  .bind(to: viewModel.email)
  .disposed(by: disposeBag)

// Just filter all events with no actual value change to prevent infinite loop
viewModel.email
  .filter { $0 != self.emailTextField.text } // if it removed whitespaces in mapping, values will not match and text in text field will be updated
  .bind(to: emailTextField.rx.text)
  .disposed(by: disposeBag)

The source code in UITextField+Rx.swift : UITextField+Rx.swift的源代码:

/// Reactive wrapper for `text` property.
    public var value: ControlProperty<String?> {
        return base.rx.controlPropertyWithDefaultEvents(
            getter: { textField in
                textField.text
            },
            setter: { textField, value in
                // This check is important because setting text value always clears control state
                // including marked text selection which is imporant for proper input 
                // when IME input method is used.
                if textField.text != value {
                    textField.text = value
                }

            }
        )
    }

The magic is in the setter:魔法在二传手:

if textField.text != value {
     textField.text = value
}

So a ControlProperty is two way binding to a Variable,所以 ControlProperty 是绑定到变量的两种方式,

The ControlProperty will not always change, because the if judgement in setter method. ControlProperty 不会一直变化,因为 setter 方法中的 if 判断。

I checked in RxSwift 5.0.1我签入了 RxSwift 5.0.1

@dengApro's answer is very close. @dengApro 的回答非常接近。

The source code in UITextField+Rx.swift : UITextField+Rx.swift的源代码:

 /// Reactive wrapper for `text` property.
    public var value: ControlProperty<String?> {
        return base.rx.controlPropertyWithDefaultEvents(
            getter: { textField in
                textField.text
            },
            setter: { textField, value in
                // This check is important because setting text value always clears control state
                // including marked text selection which is imporant for proper input 
                // when IME input method is used.
                if textField.text != value {
                    textField.text = value
                }
            }
        )
    }

Assigning textField a value could not be subscribed, because of controlPropertyWithDefaultEvents由于controlPropertyWithDefaultEvents ,无法订阅为textField分配值

The source code in UIControl+Rx.swift : UIControl+Rx.swift的源代码:

/// This is a separate method to better communicate to public consumers that
    /// an `editingEvent` needs to fire for control property to be updated.
    internal func controlPropertyWithDefaultEvents<T>(
        editingEvents: UIControl.Event = [.allEditingEvents, .valueChanged],
        getter: @escaping (Base) -> T,
        setter: @escaping (Base, T) -> Void
        ) -> ControlProperty<T> {
        return controlProperty(
            editingEvents: editingEvents,
            getter: getter,
            setter: setter
        )
    }

So just the two events UIControl.Event = [.allEditingEvents, .valueChanged] can be observable,所以只有UIControl.Event = [.allEditingEvents, .valueChanged]两个事件可以被观察到,

Variable changed, Variable bind to ControlProperty, ControlProperty changed not because of [.allEditingEvents, .valueChanged] , then done.变量改变,变量绑定到 ControlProperty,ControlProperty 改变不是因为[.allEditingEvents, .valueChanged] ,然后完成。

ControlProperty changed, ControlProperty bind to Variable, Variable changed and bind to ControlProperty,ControlProperty seted not because of [.allEditingEvents, .valueChanged] , then done. ControlProperty 更改,ControlProperty 绑定到 Variable,Variable 更改并绑定到 ControlProperty,ControlProperty 设置不是因为[.allEditingEvents, .valueChanged] ,然后完成。


In the source code of controlProperty , will establish the UIControl target - action.controlProperty的源代码中,将建立 UIControl 目标 - 动作。

[.allEditingEvents, .valueChanged] contains of editingDidBegin, editingChanged, editingDidEnd, editingDidEndOnExit, valueChanged, [.allEditingEvents, .valueChanged]包含editingDidBegin、editingChanged、editingDidEnd、editingDidEndOnExit、valueChanged、

So assigning to textField.text directly will trigger no event.所以直接赋值给textField.text不会触发任何事件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM