问题1
_ = textFiled.rx.text.subscribe(onNext: { (text) in
print("输入来了 \(text)")
})
_ = textView.rx.text.subscribe(onNext: { (text) in
print("textView:输入来了 \(text)")
})
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
textFiled.text = "Cooci"
textView.text = "LGCooci"
}
当点击屏幕的时候,textFiled的text变为了Cooci,但是并没有走到textFiled的订阅方法subscribe中去。但是textView.text变为了LGCooci,且走到了textView的订阅方法subscribe中去了。
分析
textView
点击textView的text进去看文档部分如下:
public var value: ControlProperty<String?> {
let source: Observable<String?> = Observable.deferred { [weak textView = self.base] in
let text = textView?.text
let textChanged = textView?.textStorage
// This project uses text storage notifications because
// that's the only way to catch autocorrect changes
// in all cases. Other suggestions are welcome.
由官方注释可知:这个项目使用了通知
来进行传递text的变化,因为这是唯一一种方式可以捕捉在任何情况下值的变化。欢迎提出其他建议。
textFiled
再来看textFiled的text文档,会发现一段关键代码:
/// This is a separate method to better communicate to public consumers that
/// an `editingEvent` needs to fire for control property to be updated.
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
)
}
官方注释:一个编辑事件状态即editingEvent
需要更新control
即textFiled
的属性,也即是要更新controlProperty
。
所以textFiled是根据编辑事件来响应订阅的,这里有allEditingEvents
和valueChanged
两种。其中valueChanged
看文档是对UISwitch的响应,allEditingEvents
是对textFiled的响应,因为textFiled.text = "Cooci"
只是一个赋值,textFiled并没有进入编辑状态,所以没有走到textFiled的订阅方法subscribe中去。
解决办法
在赋值的后面跟上sendActions
方法,就可以走到textFiled的订阅方法subscribe中去。如下:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
textFiled.text = "Cooci"
textFiled.sendActions(for: .allEditingEvents)
textView.text = "LGCooci"
}
问题2
_ = textFiled.rx.text
.subscribe(onNext: { (text) in
print("输入来了 \(text)")
})
/*
输入来了 Optional("") //初始化时的打印
输入来了 Optional("") //点击了textFiled后的打印
*/
上面这样写过后,输入会走两次,一次是初始化的时候,一次是点击textFiled进入编辑状态时。
分析
所有的分析,我们就需要打断点,然后看源码来进行分析。
我们来到一段关键代码:
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 { // 当前后的textField不是同一个时,就发送结束信号
observer.on(.completed)
return Disposables.create()
}
observer.on(.next(getter(control))) // skip(1) 是防止初始化的这里调起一次发送信号
// 类ControlTarget封装了textField的事件响应方法
// 初始化的时候,textField没有响应,所以没有走下面ControlTarget事件响应方法,也就不会发送信号
// 这个大括号是一个尾随闭包
let controlTarget = ControlTarget(control: control, controlEvents: editingEvents) { _ in
if let control = weakControl { // 当前后的textField是同一个时,才发送信号
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) // 是一个结构体
}
上面代码的主要关键点:
-
ControlProperty
是一个里面实现了subscribe
方法的结构体,传进去了序列和观察者两个参数,并在结构体内部实现两者之间的调度关系。 -
ControlTarget
作为一个中间类,封装了textField
的响应方法,并传了一个尾随闭包进去。 - 当只有
ControlProperty
被订阅初始化的时候,才会走到observer.on(.next(getter(control)))
这句发送信号的代码,也即是第一次打印。 - 但是当
ControlProperty
被订阅初始化的时候,textField
并没有被响应,所以不会走ControlProperty
闭包中的发送信号。
然后的关键代码就是类ControlProperty
里面的代码:
// 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()
// 添加响应事件方法,当controlEvents即UIControl.Event发送变化是,就响应selector方法,即eventHandler闭包
control.addTarget(self, action: selector, for: controlEvents)
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) // 当event发生改变的时候,一直响应这个闭包
}
}
上面代码关键点:
- 初始化的时候,保存了传进来的三个参数,并添加了响应事件方法。
- 事件方法中调用了外面传进来的闭包
callback(control)
。也就是说,每次event事件改变的时候,都走事件响应方法,即callback(control)
,也就是上一段代码中传进来的那个尾随闭包:
let controlTarget = ControlTarget(control: control, controlEvents: editingEvents) { _ in
if let control = weakControl { // 当前后的textField是同一个时,才发送信号
observer.on(.next(getter(control)))
}
}
大括号中的代码,发送信号,从而响应订阅。
解决办法
第一种:
可以注释掉源代码中的observer.on(.next(getter(control)))
这句代码,从而订阅初始化的时候,就不会有第一次打印了。
但是我们一般最好不要改源代码,所以就第二种方法。
第二种:
使用方法skip
,跳过那一次的发送信号,如下:
_ = inputTF.rx.text.skip(1)
.subscribe(onNext: { (text) in
print("输入:\(text)")
})
这样也就没有了订阅初始化时候的那第一次响应。
关键思想
使用中间类封装了textField的响应事件,当事件响应的时候,才触发方法,调用闭包,发送信号,响应订阅。
网友评论