前言
在使用Swift
的过程中,应该都使用过UITextField
这个控件,这一篇就来对这个控件在RxSwift
中的使用做个浅析。
问题
先来写一个UITextField
在RxSwift
中的基本语法:
@IBOutlet weak var textFiled: UITextField!
textFiled.rx.text.subscribe(onNext: { (text) in
print("你输入的是: \(text)")
})
复制代码
command+R
运行代码,此时先不做任何交互操作,会发现打印出了你输入的是: Optional("")
,然后点击textFiled
准备输入内容,这时候再次打印了你输入的是: Optional("")
,
textFiled
输入内容前,打印了两次空的内容,难道这是
RxSwift
的
bug
?.
再来看另外一个问题:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
textFiled.text = "i miss you"
}
复制代码
在点击屏幕的时候,我们给textFiled
赋值,看是否能订阅到赋值内容; 运行代码会发现并没有订阅到赋值的内容,但是明明已经对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()
方法,而且这个方法传入了两个参数闭包,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()
方法有三个参数,默认参数[.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)
}
}
}
复制代码
在controlTarget
的init
方法中,传入了三个参数: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:textFiled
的text
进行赋值,subscribe
闭包不执行,问题在于RxSwift
的底层是event
的封装,而对textFiled
的text
进行赋值并不是一个event
事件,而且也不是一个KVO
的形式,解决这个问题的方法可以在RxSwift
的GitHub
Issues讨论区里面看到,利用sendActions(for: .allEditingEvents)
方法:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
textFiled.text = "i miss you"
}
textFiled.sendActions(for: .allEditingEvents)
复制代码