06. RxSwift源码解读:ControlEvent、ControlProperty、Binder

今天带大家解读RxSwift中封装UI事件响应相关的源码:

ControlEvent和ControlProperty

ControlEvent 专门用于描述 UI 控件所产生的事件,ControlProperty专门描述 UI 控件属性,它们具有以下特征:

  • 不会产生 error 事件
  • 一定在 MainScheduler 订阅(主线程订阅)
  • 一定在 MainScheduler 监听(主线程监听)。
    这两个都是可观察序列。
    两者的区别在于ControlProperty可以作为观察者接受消息,比如他可以作为一个Binder;而ControlEvent不可以。看下面的例子:
        btn.rx.tap.subscribe {
            print($0)
        }
        .disposed(by: bag)
        textFiled.rx.text.subscribe {
            print($0)
        }
        .disposed(by: bag)

一个Button调用rx.tap转换成一个序列, 在源码中实际上就是包装成一个ControlEvent,这样将Button的点击事件转换成序列,并可通过subscribe订阅这个事件,所以每次点击按钮会打印next事件。

    public var tap: ControlEvent {
        controlEvent(.touchUpInside)
    }
public func controlEvent(_ controlEvents: UIControl.Event) -> ControlEvent<()> {
        let source: Observable = Observable.create { [weak control = self.base] observer in
                MainScheduler.ensureRunningOnMainThread()

                guard let control = control else {
                    observer.on(.completed)
                    return Disposables.create()
                }

                let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) { _ in
                    observer.on(.next(()))
                }

                return Disposables.create(with: controlTarget.dispose)
            }
            .take(until: deallocated)

        return ControlEvent(events: source)
    }

上面的代码将点击事件封装成可观察序列,内部通过Observable.create创建了一个可观察序列,并且将此序列所谓source保存在ControlEvent的对象中。在subscribe handler中,包装一个ControlTarget对象,ControlTarget初始化方法中对UIControl添加点击事件
control.addTarget(self, action: selector, for: controlEvents)
然后处理点击事件。

   @objc func eventHandler(_ sender: Control!) {
        if let callback = self.callback, let control = self.control {
            callback(control)
        }
    }

在callback的实现中,调用了onNext, 回到调用的地方:

let controlTarget = ControlTarget(control: control, controlEvents:     controlEvents) { _ in
                    observer.on(.next(()))
                }

这样就能产生ui点击事件,它将点击事件变换为onNext(())。

ControlEvent是一个结构体,遵循了ControlEventType协议,ControlEventType协议又继承了ObservableType,所以它是一个可观察序列。

除了上面的UIControl,任何对象都可以可序列化,只要遵循了ReactiveCompatible的协议,ReactiveCompatible实现了rx属性的get set方法,通过这种方式创建一个Reactive对象,同时Reactive对象有一个属性base,这个base就是被序列化的对象。
比如 上面的代码 btn.rx 等价于 Reactive(btn), 然后再通过扩展Reactive,创建一个可观察序列即可。

回到例子中的代码继续看textFiled.rx,同样是对UITextFiled转换成Reactive类型对象。调用text时调用了controlPropertyWithDefaultEvents

    public var value: ControlProperty {
        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
                }
            }
        )
    }

传入getter和setter闭包
getter是取出textfiled的text,setter是更新textfiled的text。

    public func controlProperty(
        editingEvents: UIControl.Event,
        getter: @escaping (Base) -> T,
        setter: @escaping (Base, T) -> Void
    ) -> ControlProperty {
        let source: Observable = Observable.create { [weak weakControl = base] observer in
                guard let control = weakControl else {
                    observer.on(.completed)
                    return Disposables.create()
                }

                observer.on(.next(getter(control)))

                let controlTarget = ControlTarget(control: control, controlEvents: editingEvents) { _ in
                    if let control = weakControl {
                        observer.on(.next(getter(control)))
                    }
                }
                
                return Disposables.create(with: controlTarget.dispose)
            }
            .take(until: deallocated)

        let bindingObserver = Binder(base, binding: setter)

        return ControlProperty(values: source, valueSink: bindingObserver)
    }

这里封装了一个ControlProperty,绑定一个Binder。如果当前ControlProperty作为一个Observer,这个Binder才有用。

ControlTarget封装了UITextField的text改变的事件,通过callback通知调用者,然后通过observer.on(.next(getter(control)))将当前UITextFiled的text值发出去。

我们进入ControlProperty结构体看看

public struct ControlProperty : ControlPropertyType {
    public typealias Element = PropertyType

    let values: Observable
    let valueSink: AnyObserver
/...

ControlProperty遵循了ControlPropertyType协议,而ControlPropertyType继承了ObservableType和ObserverType。所以既可以作为可观察序列又可以作为观察者,比如可以作为一个Binder。

作为可订阅序列,如例子中当我们订阅它时,一旦text改变,就可以接受到事件,订阅时执行的是values的订阅方法。订阅逻辑和第一章讲到的订阅逻辑一样,这里不再重述。

ControlProperty包含一个 values和一个valueSink,values是原始的observable,而且保证了在主线程执行subscribe handler。

values.subscribe(on: ConcurrentMainScheduler.instance)

valueSink是一个Binder,ControlProperty实现了on方法:

/// Binds event to user interface.
    ///
    /// - In case next element is received, it is being set to control value.
    /// - In case error is received, DEBUG buids raise fatal error, RELEASE builds log event to standard output.
    /// - In case sequence completes, nothing happens.
    public func on(_ event: Event) {
        switch event {
        case .error(let error):
            bindingError(error)
        case .next:
            self.valueSink.on(event)
        case .completed:
            self.valueSink.on(event)
        }
    }

如果将ControlProperty作为一个Observer来使用,这里的on方法会接受到事件,同时将next和completed事件转发给valueSink进行处理,valueSink是在初始化ControlProperty赋值的:

        let bindingObserver = Binder(base, binding: setter)
        return ControlProperty(values: source, valueSink: bindingObserver)

将setter闭包赋值给binding,setter就是最开始创建序列时定义的,用来设置UITextFiled的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
                }
            }

所以我们进入Binder类看一看:

/// Initializes `Binder`
    ///
    /// - parameter target: Target object.
    /// - parameter scheduler: Scheduler used to bind the events.
    /// - parameter binding: Binding logic.
    public init(_ target: Target, scheduler: ImmediateSchedulerType = MainScheduler(), binding: @escaping (Target, Value) -> Void) {
        weak var weakTarget = target

        self.binding = { event in
            switch event {
            case .next(let element):
                _ = scheduler.schedule(element) { element in
                    if let target = weakTarget {
                        binding(target, element)
                    }
                    return Disposables.create()
                }
            case .error(let error):
                rxFatalErrorInDebug("Binding error: \(error)")
            case .completed:
                break
            }
        }
    }

    /// Binds next element to owner view as described in `binding`.
    public func on(_ event: Event) {
        self.binding(event)
    }

当调用valueSink的on方法时,会调用binding方法,也就是执行setter方法,完成UITextFiled 的text更新,而且为了保证更新操作再主线程执行,上面代码中默认的调度器是MainScheduler。
下面是一个例子,演示一个UITextField绑定另一个UITextField:

        // 两个rx.text 返回的都是ControlProperty对象
        let binder = textFiled2.rx.text
        textFiled.rx.text.bind(to: binder)
        .disposed(by: bag)

UITextFiled 也可以绑定attributedText;代码实现与绑定text类似。

Binder any thing

除了可以绑定UITextFiled,我们可以封装任意属性为一个Binder,比如lable.rx.text, 只需要在对象后面调用.rx.属性名, 这样就转换成一个Binder. 如:

textFiled.rx.text.bind(to: label.rx.text)
        .disposed(by: bag)

这样text一旦发生改变,就会降其值赋值给label.text, 这就是绑定。我们看看label.rx.text方法实现

/// Automatically synthesized binder for a key path between the reactive
    /// base and one of its properties
    public subscript(dynamicMember keyPath: ReferenceWritableKeyPath) -> Binder where Base: AnyObject {
        Binder(self.base) { base, value in
            base[keyPath: keyPath] = value
        }
    }

这里利用swift的keypath实现了任意属性的绑定,代码非常简洁巧妙。
然后再看看bind方法实现:

public func bind(to observers: Observer...) -> Disposable where Observer.Element == Element {
        self.subscribe { event in
            observers.forEach { $0.on(event) }
        }
    }

实际上就是调用订阅方法,而且可以绑定多个Observer。

总结

  • ControlProperty 和 ControlEvent都是Observable的变体,它们是对Observable的封装,封装了UI事件,将UI事件封装可观察序列。另外ControlProperty可以作为观察者被绑定。
  • Binder是一个观察者,用来和可观察序列进行绑定,绑定实际上是对订阅操作的简化。

你可能感兴趣的:(06. RxSwift源码解读:ControlEvent、ControlProperty、Binder)