美文网首页RxSwift学习
RxSwift中UITextField的两个问题探索

RxSwift中UITextField的两个问题探索

作者: 简_爱SimpleLove | 来源:发表于2019-08-01 23:36 被阅读0次

    问题1

            _ = textFiled.rx.text.subscribe(onNext: { (text) in
                print("输入来了 \(text)")
            })
            _ = textView.rx.text.subscribe(onNext: { (text) in
                print("textView:输入来了 \(text)")
            })
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            textFiled.text = "Cooci"
            textView.text = "LGCooci"
        }
    

    当点击屏幕的时候,textFiled的text变为了Cooci,但是并没有走到textFiled的订阅方法subscribe中去。但是textView.text变为了LGCooci,且走到了textView的订阅方法subscribe中去了。

    分析

    textView

    点击textView的text进去看文档部分如下:

     public var value: ControlProperty<String?> {
            let source: Observable<String?> = Observable.deferred { [weak textView = self.base] in
                let text = textView?.text
                let textChanged = textView?.textStorage
                // This project uses text storage notifications because
                // that's the only way to catch autocorrect changes
                // in all cases. Other suggestions are welcome.
    

    由官方注释可知:这个项目使用了通知来进行传递text的变化,因为这是唯一一种方式可以捕捉在任何情况下值的变化。欢迎提出其他建议。

    textFiled

    再来看textFiled的text文档,会发现一段关键代码:

        /// This is a separate method to better communicate to public consumers that
        /// an `editingEvent` needs to fire for control property to be updated.
        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
            )
        }
    

    官方注释:一个编辑事件状态即editingEvent需要更新controltextFiled的属性,也即是要更新controlProperty

    所以textFiled是根据编辑事件来响应订阅的,这里有allEditingEventsvalueChanged两种。其中valueChanged看文档是对UISwitch的响应,allEditingEvents是对textFiled的响应,因为textFiled.text = "Cooci"只是一个赋值,textFiled并没有进入编辑状态,所以没有走到textFiled的订阅方法subscribe中去。

    解决办法

    在赋值的后面跟上sendActions方法,就可以走到textFiled的订阅方法subscribe中去。如下:

        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            textFiled.text = "Cooci"
            textFiled.sendActions(for: .allEditingEvents)
            textView.text = "LGCooci"
        }
    

    问题2

            _ = textFiled.rx.text
                 .subscribe(onNext: { (text) in
                print("输入来了 \(text)")
            })
            /*
             输入来了 Optional("")   //初始化时的打印
             输入来了 Optional("")   //点击了textFiled后的打印
             */
    

    上面这样写过后,输入会走两次,一次是初始化的时候,一次是点击textFiled进入编辑状态时。

    分析

    所有的分析,我们就需要打断点,然后看源码来进行分析。

    我们来到一段关键代码:

        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 { // 当前后的textField不是同一个时,就发送结束信号
                        observer.on(.completed)
                        return Disposables.create()
                    }
    
                    observer.on(.next(getter(control))) // skip(1) 是防止初始化的这里调起一次发送信号
    
                // 类ControlTarget封装了textField的事件响应方法
                // 初始化的时候,textField没有响应,所以没有走下面ControlTarget事件响应方法,也就不会发送信号
                // 这个大括号是一个尾随闭包
                    let controlTarget = ControlTarget(control: control, controlEvents: editingEvents) { _ in
                        if let control = weakControl {  // 当前后的textField是同一个时,才发送信号
                            observer.on(.next(getter(control)))
                        }
                    }
                    
                    return Disposables.create(with: controlTarget.dispose)
                }
                .takeUntil(deallocated)
    
            let bindingObserver = Binder(base, binding: setter)
    
            return ControlProperty<T>(values: source, valueSink: bindingObserver)   // 是一个结构体
        }
    

    上面代码的主要关键点:

    • ControlProperty是一个里面实现了subscribe方法的结构体,传进去了序列和观察者两个参数,并在结构体内部实现两者之间的调度关系。
    • ControlTarget作为一个中间类,封装了textField的响应方法,并传了一个尾随闭包进去。
    • 当只有ControlProperty被订阅初始化的时候,才会走到observer.on(.next(getter(control)))这句发送信号的代码,也即是第一次打印。
    • 但是当ControlProperty被订阅初始化的时候,textField并没有被响应,所以不会走ControlProperty闭包中的发送信号。

    然后的关键代码就是类ControlProperty里面的代码:

    // This should be only used from `MainScheduler`
    final class ControlTarget: RxTarget {
        typealias Callback = (Control) -> Void  // 将闭包取一个别名
    
        let selector: Selector = #selector(ControlTarget.eventHandler(_:)) // 定义了一个方法
    
        weak var control: Control?
    #if os(iOS) || os(tvOS)
        let controlEvents: UIControl.Event
    #endif
        var callback: Callback?
        #if os(iOS) || os(tvOS)
        init(control: Control, controlEvents: UIControl.Event, callback: @escaping Callback) {
            MainScheduler.ensureRunningOnMainThread()
            // 保存了传进来的参数
            self.control = control
            self.controlEvents = controlEvents
            self.callback = callback
    
            super.init()
            // 添加响应事件方法,当controlEvents即UIControl.Event发送变化是,就响应selector方法,即eventHandler闭包
            control.addTarget(self, action: selector, for: controlEvents)
    
            let method = self.method(for: selector)
            if method == nil {  // 如果没有找到方法就返回一个错误
                rxFatalError("Can't find method")
            }
        }
        @objc func eventHandler(_ sender: Control!) {
            if let callback = self.callback, let control = self.control {
                callback(control) // 当event发生改变的时候,一直响应这个闭包
            }
        }
    

    上面代码关键点:

    • 初始化的时候,保存了传进来的三个参数,并添加了响应事件方法。
    • 事件方法中调用了外面传进来的闭包callback(control)。也就是说,每次event事件改变的时候,都走事件响应方法,即callback(control),也就是上一段代码中传进来的那个尾随闭包:
                    let controlTarget = ControlTarget(control: control, controlEvents: editingEvents) { _ in
                        if let control = weakControl {  // 当前后的textField是同一个时,才发送信号
                            observer.on(.next(getter(control)))
                        }
                    }
    

    大括号中的代码,发送信号,从而响应订阅。

    解决办法

    第一种:
    可以注释掉源代码中的observer.on(.next(getter(control)))这句代码,从而订阅初始化的时候,就不会有第一次打印了。

    但是我们一般最好不要改源代码,所以就第二种方法。

    第二种:
    使用方法skip,跳过那一次的发送信号,如下:

            _ = inputTF.rx.text.skip(1)
                .subscribe(onNext: { (text) in
                print("输入:\(text)")
            })
    

    这样也就没有了订阅初始化时候的那第一次响应。

    关键思想

    使用中间类封装了textField的响应事件,当事件响应的时候,才触发方法,调用闭包,发送信号,响应订阅。

    相关文章

      网友评论

        本文标题:RxSwift中UITextField的两个问题探索

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