美文网首页RxSwiftRx
06. RxSwift源码解读:ControlEvent、Con

06. RxSwift源码解读:ControlEvent、Con

作者: Oceanj | 来源:发表于2021-06-10 13:43 被阅读0次

    今天带大家解读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<Void> {
            controlEvent(.touchUpInside)
        }
    public func controlEvent(_ controlEvents: UIControl.Event) -> ControlEvent<()> {
            let source: Observable<Void> = 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<UITextFiled>类型对象。调用text时调用了controlPropertyWithDefaultEvents

        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
                    }
                }
            )
        }
    

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

        public func controlProperty<T>(
            editingEvents: UIControl.Event,
            getter: @escaping (Base) -> T,
            setter: @escaping (Base, T) -> Void
        ) -> ControlProperty<T> {
            let source: Observable<T> = 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<T>(values: source, valueSink: bindingObserver)
        }
    
    

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

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

    我们进入ControlProperty结构体看看

    public struct ControlProperty<PropertyType> : ControlPropertyType {
        public typealias Element = PropertyType
    
        let values: Observable<PropertyType>
        let valueSink: AnyObserver<PropertyType>
    /...
    

    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<Element>) {
            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<T>(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: AnyObject>(_ 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<Value>) {
            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<Property>(dynamicMember keyPath: ReferenceWritableKeyPath<Base, Property>) -> Binder<Property> where Base: AnyObject {
            Binder(self.base) { base, value in
                base[keyPath: keyPath] = value
            }
        }
    

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

    public func bind<Observer: ObserverType>(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、Con

        本文链接:https://www.haomeiwen.com/subject/jhoxeltx.html