This article mainly discusses RxSwift inside the timer, UITextField. Rx. Text, uITextView.rx.

Three ways to realize the timer.

Before exploring the RxSwift inside the timer implementation, we first to understand the traditional implementation of the timer

1. Use NSTimer

func testNSTimer() {
       _ = Timer.scheduledTimer(withTimeInterval: 1.0, repeats:true) { (time) in
           print(time); }}Copy the code

2. Use CADisplayLink

func testLinkTimer(){
       linkTimer = CADisplayLink.init(target: self, selector: #selector(timerFire))linkTimer? .preferredFramesPerSecond = 1; linkTimer? .add(to: RunLoop.current,forMode: .default)
//        linkTimer?.isPaused = true// Pause controller} @objc functimerFire() {
       print("linkTimer")}Copy the code

3. Use the GCD

// GCD
   func testGCDTimer(){ gcdTimer = DispatchSource.makeTimerSource(); gcdTimer? .schedule(deadline: DispatchTime.now(), repeating: DispatchTimeInterval.seconds(1)) gcdTimer? .setEventHandler(handler: {print("gcd Timer") }) gcdTimer? .resume() // gcdTimer? .suspend() // suspend gcdTimer? .cancel(); // Cancel // gcdTimer = nil // Destroy (cancel() before execution)}Copy the code

The realization of RxSwift timer

Using 1,2, we found that UI slide would block the execution of the timer, but using GCD was not affected by UI slide, so we dared to guess that the timer in RxSwift is encapsulated with GCD.

var timer: Observable<Int>!
func setupTimer(){// Implementation principle? timer = Observable<Int>.interval(1, scheduler: MainScheduler.init()) timer.subscribe(onNext: { (num)in
           print("Timer :\(num)")
       }).disposed(by: disposeBag)
   }
Copy the code

Let’s take a look at the implementation (it’s important to understand the core logic of RxSwift)

Step by step analysis of the code inside, we will find that RxSwift timer is the encapsulation of GCD

** is the following core code to implement the timer, which we will find is a GCD wrapper **

func schedulePeriodic<StateType>(_ state: StateType, startAfter: RxTimeInterval, period: RxTimeInterval, action: @escaping (StateType) -> StateType) -> Disposable {
       let initial = DispatchTime.now() + startAfter
       var timerState = state
       let timer = DispatchSource.makeTimerSource(queue: self.queue)
       timer.schedule(deadline: initial, repeating: period, leeway: self.leeway)
       ...
       timer.setEventHandler(handler: {
           if cancelTimer.isDisposed {
               return
           }
           timerState = action(timerState)
       })
       timer.resume()
       
       return cancelTimer
   }
Copy the code

TextField.rx

Step by step into parsing the source code, we find textField.rx.text implementation:

/ / in the UIControl + Rx. Swift internal inside 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

The principle of TextView.rx. Text

The final encapsulation of TextView.rx.text is:

// UITextView+Rx.swift public var value: ControlProperty<String? > {let source: Observable<String? > = Observable.deferred { [weak textView = self.base]in
          lettext = textView? .textlettextChanged = textView? .textStorage .rx.didProcessEditingRangeChangeInLength .observeOn(MainScheduler.asyncInstance) .map { _in
                  returntextView? .textStorage.string } ?? Observable.empty()return textChanged
              .startWith(text)
      }
Copy the code

Look at didProcessEditingRangeChangeInLength implementation, is actually the textView? .textStorage value listener

      //NSTextStorage+Rx.swift
      public var didProcessEditingRangeChangeInLength: Observable<(editedMask: NSTextStorage.EditActions, editedRange: NSRange, delta: Int)> {
          return delegate
              .methodInvoked(#selector(NSTextStorageDelegate.textStorage(_:didProcessEditing:range:changeInLength:)))
              .map { a in
                  let editedMask = NSTextStorage.EditActions(rawValue: try castOrThrow(UInt.self, a[1]) )
                  let editedRange = try castOrThrow(NSValue.self, a[2]).rangeValue
                  let delta = try castOrThrow(Int.self, a[3])
                  
                  return (editedMask, editedRange, delta)
              }
      }
Copy the code

thinking

Why is it executed twice before typing?