通常 RxSwift 用於 MVVM, MVVM 經常使用功能就是雙向綁定,Model 和 UI 的相互數據關聯。swift
<->
在 RxSwift 的案例代碼中,有一個 Operators.swift
文件,提供了一個 <->
雙向綁定操做符函數。api
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 訂閱了模型的事件。bash
property.subscribe
, 模型訂閱了 UI 的事件。app
就這樣,雙向綁定完成了。函數
打個比方,以下代碼,模型與 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 ...spa
其實是沒有死循環的。線程
能夠簡單理解爲 textFieldOne.rx.text
作了保護。雙向綁定
下面是 UITextField+Rx.swift
的源代碼。code
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
, 就完了。
仍是 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")
}
}
...
複製代碼