看下 RxSwift 的雙向綁定, 及 RxCocoa 的相關源代碼

通常 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, bindRxCocoasubscribe 的封裝,換句話,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, 就完了。

再看下, 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")
        }
    }


...
複製代碼
相關文章
相關標籤/搜索