preface

In the process of using Swift, should have used UITextField this control, this article to this control in RxSwift in the use of a brief analysis.

The problem

Here’s the basic syntax for UITextField in RxSwift:

@IBOutlet weak var textFiled: UITextField!

textFiled.rx.text.subscribe(onNext: { (text) in
        print("You typed: \(text)")})Copy the code

So command+R is going to run this code, and you’re not going to do any interaction, and you’re going to print it out and you’re going to type in Optional(“”), and then textFiled, and you’re going to print it out again and you’re going to type in Optional(“”),

textFiled
RxSwift
bug

Here’s another question:

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

When we click on the screen, we assign textFiled to see if we can subscribe to the assigned content; TextFiled is not subscribed to the assigned content, but it has been subscribed to textFiled. If the text value is changed, it cannot be subscribed.

Analysis of the

First, click the text of Textfiled.rx. text to see the source code process.

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

This returns value, which is equivalent to the getter for value, and then hits value to follow:

/// 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.
                iftextField.text ! = value { textField.text = value } } ) }Copy the code

Here to return to the controlPropertyWithDefaultEvents () method, and this method was introduced into the two parameters closures, getter and setter, to enter controlPropertyWithDefaultEvents () this approach,

 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
        )
    }
Copy the code

Can see this controlPropertyWithDefaultEvents () method has three parameters, the default argument [. AllEditingEvents,. ValueChanged], as well as the incoming getter closures, setter closure; And this method returns the controlProperty() method, so let’s go through the implementation of this controlProperty() method again;

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)
    }
Copy the code

If you notice the break point in this method, ·return ControlProperty() is executed before it goes to the closure where observable.create () creates the sequence, . That is to say as long as the executive textFiled rx. Text. The subscribe (), is the inevitable result to enter controlProperty () method, in this method, create closure method of sequence, Observer.on (.next(getter(control)))) will be executed once. Next will send onNext signal once.

Follow the code to create a controlTarget, follow the source code to see the initialization of the 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, forControlEvents) // Add event binding eventHandler methodlet 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)
        }
    }
}
Copy the code

In the controlTarget init method, three parameters are passed: Control, [.alleditingEvents,.valuechanged], WeakControl {observer.on(.next(getter(control)))}} in if let control = weakControl {observer.on(.next(getter(control)))}} Then control.addTarget(self, action: selector, for: ControlEvents), add an event to the control that is passed in, bind a method, and the control is the textFiled that is created, add an event bound method to textFiled, and as soon as it starts to enjoy it, it will execute the selector method, But here the binding of the selector is # selector (ControlTarget. EventHandler (_) method, That is to say event triggers will come ControlTarget. EventHandler (_), in the eventHandler (_) method, implement the callback (control) closure;

{_in
      if let control = weakControl {
        observer.on(.next(getter(control)))
        }
    }
Copy the code

Observer.on (.next(getter(Control)))) inside the closure, again.next, which says onNext will be sent once (this is where the second print is blank)

conclusion

From the above analysis, we can see that the blank content is printed twice for question 1: TextFiled. Rx. Text will execute onNext to send signals and output blank content when it is subscribed. TextFiled response will execute onNext to send signals and output blank content when it is clicked. It is necessary to ignore the first output, and the solution is to use skip();

textFiled.rx.text.skip(1).subscribe(onNext: { (text) in
        print("You typed: \(text)")})Copy the code

For question 2: TextFiled text is assigned, but the subscribe closure is not executed. The problem is that RxSwift is the encapsulation of event at the bottom, and assigning value to textFiled text is not an event event, nor is it a KVO. The solution to this problem can be found in the GitHub Issues section of RxSwift, using sendActions(for:.alleditingEvents) :

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