一般 RxSwift 用于 MVVM, MVVM 常用功能就是双向绑定,Model 和 UI 的相互数据关联。
看下官方的 <->
在 RxSwift 的案例代码中,有一个 Operators.swift 文件,提供了一个 <-> 双向绑定操作符函数。
func <-> <T>(property: ControlProperty<T>, relay: BehaviorRelay<T>) -> Disposable {
let bindToUIDisposable = relay.bind(to: property)
let bindToRelay = property
.subscribe(onNext: { n in
relay.accept(n)
}, onCompleted: {
bindToUIDisposable.dispose()
})
return Disposables.create(bindToUIDisposable, bindToRelay)
}
代码逻辑很清晰,relay.bind(to: property), 模型绑定 UI, bind 是 RxCocoa 对 subscribe 的封装,换句话,UI 订阅了模型的事件。
property.subscribe, 模型订阅了 UI 的事件。
就这样,双向绑定完成了。
为什么不会陷入事件循环?
打个比方,如下代码,模型与 UI 绑定,点击按钮改模型,给模型传入一个事件。
(textFieldOne.rx.text <-> messageVal).disposed(by: disposeBag)
btn.rx.tap.subscribe(onNext: { (_) in
self.messageVal.accept("Go")
}).disposed(by: disposeBag)
模型 messageVal 收到一个事件,传给 UI textFieldOne.rx.text, textFieldOne.rx.text UI 传给模型 messageVal,模型 messageVal 再次传给 UI ...
实际上是没有死循环的。
可以简单理解为 textFieldOne.rx.text 做了保护。
下面是 UITextField+Rx.swift 的源代码。
extension Reactive where Base: UITextField {
/// Reactive wrapper for `text` property.
public var text: ControlProperty<String?> {
return value
}
/// 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
}
}
)
}
textFieldOne.rx.text 里面的 .text, 是一个计算属性,是 value 起作用。
value 又是一个计算属性,计算属性就是方法( getter/ setter 函数),
直观的看到一个 if if textField.text != value {, 这样不会老是要把 textField 拎出来写入。
起作用的是 base.rx.controlPropertyWithDefaultEvents,
UIControl+Rx.swift 的源代码:
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
)
}
结论: 这里只用到了 [.allEditingEvents, .valueChanged] 两种事件 UIControl.Event .
然后对 UIControl 建立 target action 机制,因为只有编辑(开始编辑,结束编辑,编辑变化)和修改(发射不同的值)的控件事件,所以直接对 textField.text 赋值,是订阅不到的。
这样改模型 messageVal,模型 messageVal 收到一个事件,传给 UI textFieldOne.rx.text, 就完了。
这样改UI textFieldOne.rx.text,UI textFieldOne.rx.text 收到一个事件,传给 模型 messageVal,模型 messageVal 收到 UI 一个事件,再次传给 UI textFieldOne.rx.text, 就完了。
再看下, RxCocoa 是怎样建立 Target Action 的
还是 UIControl+Rx.swift 文件
/// Creates a `ControlProperty` that is triggered by target/action pattern value updates.
///
/// - parameter controlEvents: Events that trigger value update sequence elements.
/// - parameter getter: Property value getter.
/// - parameter setter: Property value setter.
public func controlProperty<T>(
editingEvents: UIControl.Event,
getter: @escaping (Base) -> T,
setter: @escaping (Base, T) -> Void
) -> ControlProperty<T> {
// 处理 getter
let source: Observable<T> = Observable.create { [weak weakControl = base] observer in
guard let control = weakControl else {
observer.on(.completed)
return Disposables.create()
}
// 上面是内存管理,避免循环引用的 weak strong dance, 下面开始做正事
observer.on(.next(getter(control)))
// 建立 target - action
let controlTarget = ControlTarget(control: control, controlEvents: editingEvents) { _ in
if let control = weakControl {
observer.on(.next(getter(control)))
}
}
return Disposables.create(with: controlTarget.dispose)
}
.takeUntil(deallocated)
// 处理 setter
let bindingObserver = Binder(base, binding: setter)
return ControlProperty<T>(values: source, valueSink: bindingObserver)
}
在 ControlTarget.swift 文件中,添加 target action 相对简单清晰
final class ControlTarget: RxTarget {
typealias Callback = (Control) -> Void
let selector: Selector = #selector(ControlTarget.eventHandler(_:))
weak var control: Control?
let controlEvents: UIControl.Event
var callback: Callback?
init(control: Control, controlEvents: UIControl.Event, callback: @escaping Callback) {
// 确保主线程
MainScheduler.ensureRunningOnMainThread()
// init 属性
self.control = control
self.controlEvents = controlEvents
self.callback = callback
super.init()
// 添加 target action
control.addTarget(self, action: selector, for: controlEvents)
let method = self.method(for: selector)
if method == nil {
rxFatalError("Can't find method")
}
}
...