美文网首页RxSwift学习
RxSwift学习插曲--UITextField的两次输出

RxSwift学习插曲--UITextField的两次输出

作者: Henry_Jeannie | 来源:发表于2019-08-03 09:41 被阅读0次

    前言

    在使用Swift的过程中,应该都使用过UITextField这个控件,这一篇就来对这个控件在RxSwift中的使用做个浅析。

    问题

    先来写一个UITextFieldRxSwift中的基本语法:

    @IBOutlet weak var textFiled: UITextField!
    
    textFiled.rx.text.subscribe(onNext: { (text) in
            print("你输入的是: \(text)")
        })
    

    command+R运行代码,此时先不做任何交互操作,会发现打印出了你输入的是: Optional(""),然后点击textFiled准备输入内容,这时候再次打印了你输入的是: Optional(""),

    image
    之后输入内容,正常打印出的就是输入的内容, image
    也就是说在textFiled输入内容前,打印了两次空的内容,难道这是RxSwiftbug😄.

    再来看另外一个问题:

      override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            textFiled.text = "i miss you"
        }
    

    在点击屏幕的时候,我们给textFiled赋值,看是否能订阅到赋值内容;
    运行代码会发现并没有订阅到赋值的内容,但是明明已经对textFiledtext进行订阅了,text值有改变的话,居然订阅不到?

    分析

    首先,点击textFiled.rx.texttext进去看源码流程,

    /// Reactive wrapper for `text` property.
     public var text: ControlProperty<String?> {
            return value
    }
    

    这里返回了value,相当于是valuegetter方法,再点击value跟进去:

    /// Reactive wrapper for `text` property.
        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
                    }
                }
            )
        }
    

    这里返回了controlPropertyWithDefaultEvents()方法,而且这个方法传入了两个参数闭包,gettersetter,再跟进去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
            )
        }
    

    可以看到这个controlPropertyWithDefaultEvents()方法有三个参数,默认参数[.allEditingEvents, .valueChanged],以及传入的getter闭包,setter闭包;而这个方法会返回controlProperty()方法,再次跟进去看一下这个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(.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)
                }
                .takeUntil(deallocated)
            let bindingObserver = Binder(base, binding: setter)
    
            return ControlProperty<T>(values: source, valueSink: bindingObserver)
        }
    

    如果在这个方法里打断点可以发现,这里是先执行·return ControlProperty(),然后才会来到Observable.create()创建序列的闭包内执行,也就是说只要执行textFiled.rx.text.subscribe(),就必然会进入到controlProperty()方法中,在这个方法里,创建序列的闭包方法内,会执行observer.on(.next(getter(control)))这句代码,这句代码就会执行一次.next,也就是说明会发送一次onNext信号,(这里就是执行的第一次打印空白的地方)

    跟着代码继续往下看,会创建一个controlTarget,跟进去源码看一下controlTarget的初始化;

    // 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()
            control.addTarget(self, action: selector, for: controlEvents) // 添加事件 绑定eventHandler方法
            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)
            }
        }
    }
    

    controlTargetinit方法中,传入了三个参数:control[.allEditingEvents, .valueChanged],以及传入的闭包{ _ in if let control = weakControl { observer.on(.next(getter(control))) } },这里会先将这三个参数保存下来,然后执行control.addTarget(self, action: selector, for: controlEvents),给传入的control添加事件,绑定方法,这里的control就是创建的textFiled,也就是给textFiled添加事件绑定方法,只要它开始享有就会执行selector方法,而此处绑定的selector#selector(ControlTarget.eventHandler(_:))方法,也就是说事件触发会来到ControlTarget.eventHandler(_:),在eventHandler(_:)方法内,执行callback(control)执行闭包;

    { _ in
          if let control = weakControl {
            observer.on(.next(getter(control)))
            }
        }
    

    在闭包内执行observer.on(.next(getter(control))),这里再次.next,也就是说明会发送一次onNext信号,(这里就是执行的第二次打印空白的地方)

    总结

    从上面的分析中可以知道,对于问题1输出两次空白内容:在textFiled.rx.text订阅的时候,会执行一次onNext,发送信号,输出一次空白内容,在点击textFiled响应的时候会执行一次onNext,发送信号,输出一次空白内容,那么就有必要忽略掉第一次输出,解决方法是使用skip()方法;

    textFiled.rx.text.skip(1).subscribe(onNext: { (text) in
            print("你输入的是: \(text)")
        })
    

    对于问题2:textFiledtext进行赋值,subscribe闭包不执行,问题在于RxSwift的底层是event的封装,而对textFiledtext进行赋值并不是一个event事件,而且也不是一个KVO的形式,解决这个问题的方法可以在RxSwiftGitHub Issues讨论区里面看到,利用sendActions(for: .allEditingEvents)方法:

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            textFiled.text = "i miss you"
        }
    textFiled.sendActions(for: .allEditingEvents)
    
    image

    相关文章

      网友评论

        本文标题:RxSwift学习插曲--UITextField的两次输出

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