前言
在使用Swift的過程中,應(yīng)該都使用過UITextField這個控件,這一篇就來對這個控件在RxSwift中的使用做個淺析。
問題
先來寫一個UITextField在RxSwift中的基本語法:
@IBOutlet weak var textFiled: UITextField!
textFiled.rx.text.subscribe(onNext: { (text) in
print("你輸入的是: \(text)")
})
command+R運行代碼,此時先不做任何交互操作,會發(fā)現(xiàn)打印出了你輸入的是: Optional(""),然后點擊textFiled準備輸入內(nèi)容,這時候再次打印了你輸入的是: Optional(""),
之后輸入內(nèi)容,正常打印出的就是輸入的內(nèi)容,
也就是說在
textFiled輸入內(nèi)容前,打印了兩次空的內(nèi)容,難道這是RxSwift的bug??.
再來看另外一個問題:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
textFiled.text = "i miss you"
}
在點擊屏幕的時候,我們給textFiled賦值,看是否能訂閱到賦值內(nèi)容;
運行代碼會發(fā)現(xiàn)并沒有訂閱到賦值的內(nèi)容,但是明明已經(jīng)對textFiled的text進行訂閱了,text值有改變的話,居然訂閱不到?
分析
首先,點擊textFiled.rx.text的text進去看源碼流程,
/// Reactive wrapper for `text` property.
public var text: ControlProperty<String?> {
return value
}
這里返回了value,相當于是value的getter方法,再點擊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()方法,而且這個方法傳入了兩個參數(shù)閉包,getter和setter,再跟進去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()方法有三個參數(shù),默認參數(shù)[.allEditingEvents, .valueChanged],以及傳入的getter閉包,setter閉包;而這個方法會返回controlProperty()方法,再次跟進去看一下這個controlProperty()方法的實現(xiàn);
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)
}
如果在這個方法里打斷點可以發(fā)現(xiàn),這里是先執(zhí)行·return ControlProperty(),然后才會來到Observable.create()創(chuàng)建序列的閉包內(nèi)執(zhí)行,也就是說只要執(zhí)行textFiled.rx.text.subscribe(),就必然會進入到controlProperty()方法中,在這個方法里,創(chuàng)建序列的閉包方法內(nèi),會執(zhí)行observer.on(.next(getter(control)))這句代碼,這句代碼就會執(zhí)行一次.next,也就是說明會發(fā)送一次onNext信號,(這里就是執(zhí)行的第一次打印空白的地方)
跟著代碼繼續(xù)往下看,會創(chuàng)建一個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)
}
}
}
在controlTarget的init方法中,傳入了三個參數(shù):control,[.allEditingEvents, .valueChanged],以及傳入的閉包{ _ in if let control = weakControl { observer.on(.next(getter(control))) } },這里會先將這三個參數(shù)保存下來,然后執(zhí)行control.addTarget(self, action: selector, for: controlEvents),給傳入的control添加事件,綁定方法,這里的control就是創(chuàng)建的textFiled,也就是給textFiled添加事件綁定方法,只要它開始享有就會執(zhí)行selector方法,而此處綁定的selector是#selector(ControlTarget.eventHandler(_:))方法,也就是說事件觸發(fā)會來到ControlTarget.eventHandler(_:),在eventHandler(_:)方法內(nèi),執(zhí)行callback(control)執(zhí)行閉包;
{ _ in
if let control = weakControl {
observer.on(.next(getter(control)))
}
}
在閉包內(nèi)執(zhí)行observer.on(.next(getter(control))),這里再次.next,也就是說明會發(fā)送一次onNext信號,(這里就是執(zhí)行的第二次打印空白的地方)
總結(jié)
從上面的分析中可以知道,對于問題1輸出兩次空白內(nèi)容:在textFiled.rx.text訂閱的時候,會執(zhí)行一次onNext,發(fā)送信號,輸出一次空白內(nèi)容,在點擊textFiled響應(yīng)的時候會執(zhí)行一次onNext,發(fā)送信號,輸出一次空白內(nèi)容,那么就有必要忽略掉第一次輸出,解決方法是使用skip()方法;
textFiled.rx.text.skip(1).subscribe(onNext: { (text) in
print("你輸入的是: \(text)")
})
對于問題2:textFiled的text進行賦值,subscribe閉包不執(zhí)行,問題在于RxSwift的底層是event的封裝,而對textFiled的text進行賦值并不是一個event事件,而且也不是一個KVO的形式,解決這個問題的方法可以在RxSwift的GitHub Issues討論區(qū)里面看到,利用sendActions(for: .allEditingEvents)方法:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
textFiled.text = "i miss you"
}
textFiled.sendActions(for: .allEditingEvents)