美文网首页RxswiftRxSwift学习
04Rxswift的textField执行两次问题

04Rxswift的textField执行两次问题

作者: 越来越胖了 | 来源:发表于2019-08-03 23:08 被阅读0次

    问题:对textField进行订阅,发现点击textField会默认先执行了两次,一次是程序启动后textField初始化时,一次是成为第一响应时,如下:

    textFiled.rx.text.subscribe(onNext: { (text) in
                print("text==  \(text ?? "")")
            })
            .disposed(by: disposeBag)
    
    --------------输入12,打印,为什么会有两次为空的打印??----------------
    text== 
    text== 
    text== 1
    text== 12
    

    问题二:直接修改textFiled.text,为什么没有触发订阅??(textView会触发)

      override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            textFiled.text = "touche--textField值改变"
        }
    

    首先为什么textFiledtextView有区别?

    ------------textView.rx.text内部------------
    public var text: ControlProperty<String?> {
            return value
        }
        
        /// Reactive wrapper for `text` property.
        public var value: ControlProperty<String?> {
            let source: Observable<String?> = Observable.deferred { [weak textView = self.base] in
                let text = textView?.text
                
                let textChanged = textView?.textStorage
    //注释中提到了,使用的是通知notifications,欢迎其他建议.
                    // This project uses text storage notifications because
                    // that's the only way to catch autocorrect changes
                    // in all cases. Other suggestions are welcome.
                    .rx.didProcessEditingRangeChangeInLength
                    // This observe on is here because text storage
                    // will emit event while process is not completely done,
                    // so rebinding a value will cause an exception to be thrown.
                    .observeOn(MainScheduler.asyncInstance)
                    .map { _ in
                        return textView?.textStorage.string
                    }
                    ?? Observable.empty()
                
                return textChanged
                    .startWith(text)
            }
    ------------textField.rx.text内部------------
     internal func controlPropertyWithDefaultEvents<T>(
    //通过event事件处理的,而且就两个 [.allEditingEvents, .valueChanged],
            editingEvents: UIControl.Event = [.allEditingEvents, .valueChanged],
            getter: @escaping (Base) -> T,
            setter: @escaping (Base, T) -> Void
            ) -> ControlProperty<T> {
            return controlProperty(
                editingEvents: editingEvents,
                getter: getter,
                setter: setter
            )
        }
    

    textView的通知是监听文本的改变发送通知,textField则是event事件触发的,所以不一样,直接修改值并没有触发event事件。textFieldEvent 中的valueChanged其实是没有意义的,因为textField并没有valueChangedevent事件(滑块就有),放在这只是因为UIControl的枚举中有这个valueChanged而已。

    解决textField值改变后能响应事件的办法:改变值后添加代码textFiled.sendActions(for: .allEditingEvents),意思就是发生一个allEditingEvents事件出去。

    下面主要介绍下为什么会textField有两次触发,源代码如下:

    -----------------进入textFiled.rx.text方法-----------------
    public var text: ControlProperty<String?> {
            return value
        }
    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
                    }
                }
            )
        }
    
    ----------------- base.rx.controlPropertyWithDefaultEvents  -----------------
    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
            )
        }
    
    ----------------- controlProperty 最最核心的代码,所有的答案都在这里-----------------
    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
                    observer.on(.next(getter(control)))
    
                    let controlTarget = ControlTarget(control: control, controlEvents: editingEvents) { _ in
                        if let control = weakControl {
                          //  第二次observer.on
                            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)
        }
    

    进入到上面的核心代码,好长,这样来看就好多了:

    public func controlProperty<T>(
            editingEvents: UIControl.Event,
            getter: @escaping (Base) -> T,
            setter: @escaping (Base, T) -> Void
        ) -> ControlProperty<T> {
            let source: Observable<T> = Observable.create {...}
                .takeUntil(deallocated)
    
            let bindingObserver = Binder(base, binding: setter)
    
            return ControlProperty<T>(values: source, valueSink: bindingObserver)
        }
    

    通过create创建序列,通过Binder订阅创建观察者(和subscribe类似的一个方法),所以会执行第一次observer.on(代码里面我标记了),所以会输出一次,我们叫做是rx的textField初始化
    第二次则是在我们点击textField时,主要分析代码:

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

    方法传入了controlevent和一个闭包observer.on(.next(getter(control))),我们想要知道的就是闭包在什么时候回被调用,上代码:

    ----------------ControlTarget的实现----------------
    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()
    
            control.addTarget(self, action: selector, for: controlEvents)
    
            let method = self.method(for: selector)
            if method == nil {
                rxFatalError("Can't find method")
            }
        }
    #elseif os(macOS)
        init(control: Control, callback: @escaping Callback) {...}
    #endif
    
        @objc func eventHandler(_ sender: Control!) {
            if let callback = self.callback, let control = self.control {
                callback(control)
            }
        }
    
        override func dispose() {...}
    }
    
    

    通过init初始化后,看到闭包指向了Callback,也就是self.callback,所以我们希望这个方法被调用。
    control.addTarget(self, action: selector, for: controlEvents)给textField绑定了一个事件selector,通过let selector: Selector = #selector(ControlTarget.eventHandler(_:))selector指向eventHandlereventHandler调用了callback(control)返回自身,so 闭包被调用了,问题完美解决。

    总结:第一次是初始化调用observer.on引起的;第二次是点击了textField引起的event事件响应。如有写的不对的地方,烦请不吝赐教,本人不胜感激~~~

    相关文章

      网友评论

        本文标题:04Rxswift的textField执行两次问题

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