看下 RxSwift 的双向绑定, 及 RxCocoa 的相关源代码

2,624 阅读3分钟

一般 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, bindRxCocoasubscribe 的封装,换句话,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")
        }
    }


...