RxSwift 中的两种方式绑定
Two way binding in 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)
}
当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)
}
}
}
我的测试设置如下,我删除了所有位于此处的视图以使其更短:
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)
}
现在要出结果了。首先,我们尝试点击按钮,这会将变量设置为 true
。这会在 ControlProperty 上触发 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`)
接下来让我们触发开关本身。因此,正如我们所见,控件生成了一个事件作为 UIControlEventValueChanged
的结果,该事件通过 ControlProperty 上的 _values
传递,然后它的值被分配给 Variable
值,如上例所示。但是没有循环,因为更新 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`)
所以一个简单的解释是:
- 一旦某种
UIControlEvent
被触发 ,就会发出来自控件的值
- 当一个值直接分配给控件时 属性,控件不会触发更改事件,因此没有循环。
希望对您有所帮助,抱歉解释有点乱 - 我是通过实验发现的)
我相信你可以使用 bindTo
。以下是 ControlProperty <-> Variable
和 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)
}
ControlProperty <-> Variable
的示例(例如 UITextField
和 UITextView
)在 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
你输入任何内容5秒后就会清楚。这是从上面
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([=10=]) }.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([=10=]) },
onCompleted: { variableToProperty.dispose() }
)
return Disposables.create(variableToProperty, propertyToVariable)
}
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
}
}
)
}
魔法就在setter:
if textField.text != value {
textField.text = value
}
所以 ControlProperty 是绑定到变量的两种方式,
ControlProperty不会一直变化,因为setter方法中的if判断
我检查了 RxSwift 5.0.1
@dengApro的回答很接近
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
}
}
)
}
分配 textField
值无法订阅,因为controlPropertyWithDefaultEvents
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
)
}
所以只有 UIControl.Event = [.allEditingEvents, .valueChanged]
这两个事件是可以观察到的,
变量已更改,变量绑定到 ControlProperty,ControlProperty 更改不是因为 [.allEditingEvents, .valueChanged]
,然后完成。
ControlProperty 已更改,ControlProperty 绑定到变量,变量已更改并绑定到 ControlProperty,ControlProperty 设置不是因为 [.allEditingEvents, .valueChanged]
,然后完成。
在controlProperty
的源代码中,会建立UIControl的目标-action。
[.allEditingEvents, .valueChanged]
包含 editingDidBegin、editingChanged、editingDidEnd、editingDidEndOnExit、valueChanged,
所以直接赋值给textField.text
不会触发任何事件
绑定BehaviourRelay
回到控制属性没有障碍。您只需要过滤具有相同值的事件(以防止无限循环)。
例如,在我的例子中,我需要将电子邮件绑定到文本字段。但我想在电子邮件输入过程中删除空格。
这是我如何实现它的示例:
emailTextField.rx.text
.map { [=10=]?.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 { [=10=] != 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)
我在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)
}
当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)
}
}
}
我的测试设置如下,我删除了所有位于此处的视图以使其更短:
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)
}
现在要出结果了。首先,我们尝试点击按钮,这会将变量设置为 true
。这会在 ControlProperty 上触发 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`)
接下来让我们触发开关本身。因此,正如我们所见,控件生成了一个事件作为 UIControlEventValueChanged
的结果,该事件通过 ControlProperty 上的 _values
传递,然后它的值被分配给 Variable
值,如上例所示。但是没有循环,因为更新 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`)
所以一个简单的解释是:
- 一旦某种
UIControlEvent
被触发 ,就会发出来自控件的值
- 当一个值直接分配给控件时 属性,控件不会触发更改事件,因此没有循环。
希望对您有所帮助,抱歉解释有点乱 - 我是通过实验发现的)
我相信你可以使用 bindTo
。以下是 ControlProperty <-> Variable
和 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)
}
ControlProperty <-> Variable
的示例(例如 UITextField
和 UITextView
)在 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
你输入任何内容5秒后就会清楚。这是从上面
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([=10=]) }.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([=10=]) },
onCompleted: { variableToProperty.dispose() }
)
return Disposables.create(variableToProperty, propertyToVariable)
}
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
}
}
)
}
魔法就在setter:
if textField.text != value {
textField.text = value
}
所以 ControlProperty 是绑定到变量的两种方式,
ControlProperty不会一直变化,因为setter方法中的if判断
我检查了 RxSwift 5.0.1
@dengApro的回答很接近
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
}
}
)
}
分配 textField
值无法订阅,因为controlPropertyWithDefaultEvents
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
)
}
所以只有 UIControl.Event = [.allEditingEvents, .valueChanged]
这两个事件是可以观察到的,
变量已更改,变量绑定到 ControlProperty,ControlProperty 更改不是因为 [.allEditingEvents, .valueChanged]
,然后完成。
ControlProperty 已更改,ControlProperty 绑定到变量,变量已更改并绑定到 ControlProperty,ControlProperty 设置不是因为 [.allEditingEvents, .valueChanged]
,然后完成。
在controlProperty
的源代码中,会建立UIControl的目标-action。
[.allEditingEvents, .valueChanged]
包含 editingDidBegin、editingChanged、editingDidEnd、editingDidEndOnExit、valueChanged,
所以直接赋值给textField.text
不会触发任何事件
绑定BehaviourRelay
回到控制属性没有障碍。您只需要过滤具有相同值的事件(以防止无限循环)。
例如,在我的例子中,我需要将电子邮件绑定到文本字段。但我想在电子邮件输入过程中删除空格。 这是我如何实现它的示例:
emailTextField.rx.text
.map { [=10=]?.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 { [=10=] != 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)