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?