RxswiftRxSwift学习

04Rxswift的textField执行两次问题

2019-08-03  本文已影响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事件响应。如有写的不对的地方,烦请不吝赐教,本人不胜感激~~~

上一篇 下一篇

猜你喜欢

热点阅读