看下 RxSwift 的雙向綁定, 及 RxCocoa 的相關源代碼
一般 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")
}
}
...
看下 RxBiBinding 源代碼,
上正菜,RxBiBinding 源代碼
RxBiBinding 作為專業做雙向綁定的,提供了三種場景,模型( 行為主體 )綁定模型,控件綁定控件,模型綁定控件。
// 控件綁定控件
public func <-><E>(left: ControlProperty<E>, right: ControlProperty<E>) -> Disposable {}
// 模型綁定模型
public func <-><E>(left: BehaviorRelay<E>, right: BehaviorRelay<E>) -> Disposable {}
// 模型綁定控件
public func <-><E>(left: ControlProperty<E>, right: BehaviorRelay<E>) -> Disposable {}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
涉及的框架與服務,真是多啊。
WXApiService
// 微信服務UMSocialService
// 友盟相關TingyunAppService
// 聽云sdk接入