06. RxSwift源码解读:ControlEvent、Con
今天带大家解读RxSwift中封装UI事件响应相关的源码:
ControlEvent和ControlProperty
ControlEvent 专门用于描述 UI 控件所产生的事件,ControlProperty专门描述 UI 控件属性,它们具有以下特征:
- 不会产生 error 事件
- 一定在 MainScheduler 订阅(主线程订阅)
- 一定在 MainScheduler 监听(主线程监听)。
这两个都是可观察序列。
两者的区别在于ControlProperty可以作为观察者接受消息,比如他可以作为一个Binder;而ControlEvent不可以。看下面的例子:
btn.rx.tap.subscribe {
print($0)
}
.disposed(by: bag)
textFiled.rx.text.subscribe {
print($0)
}
.disposed(by: bag)
一个Button调用rx.tap转换成一个序列, 在源码中实际上就是包装成一个ControlEvent,这样将Button的点击事件转换成序列,并可通过subscribe订阅这个事件,所以每次点击按钮会打印next事件。
public var tap: ControlEvent<Void> {
controlEvent(.touchUpInside)
}
public func controlEvent(_ controlEvents: UIControl.Event) -> ControlEvent<()> {
let source: Observable<Void> = Observable.create { [weak control = self.base] observer in
MainScheduler.ensureRunningOnMainThread()
guard let control = control else {
observer.on(.completed)
return Disposables.create()
}
let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) { _ in
observer.on(.next(()))
}
return Disposables.create(with: controlTarget.dispose)
}
.take(until: deallocated)
return ControlEvent(events: source)
}
上面的代码将点击事件封装成可观察序列,内部通过Observable.create创建了一个可观察序列,并且将此序列所谓source保存在ControlEvent的对象中。在subscribe handler中,包装一个ControlTarget对象,ControlTarget初始化方法中对UIControl添加点击事件
control.addTarget(self, action: selector, for: controlEvents)
然后处理点击事件。
@objc func eventHandler(_ sender: Control!) {
if let callback = self.callback, let control = self.control {
callback(control)
}
}
在callback的实现中,调用了onNext, 回到调用的地方:
let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) { _ in
observer.on(.next(()))
}
这样就能产生ui点击事件,它将点击事件变换为onNext(())。
ControlEvent是一个结构体,遵循了ControlEventType协议,ControlEventType协议又继承了ObservableType,所以它是一个可观察序列。
除了上面的UIControl,任何对象都可以可序列化,只要遵循了ReactiveCompatible的协议,ReactiveCompatible实现了rx属性的get set方法,通过这种方式创建一个Reactive对象,同时Reactive对象有一个属性base,这个base就是被序列化的对象。
比如 上面的代码 btn.rx
等价于 Reactive(btn)
, 然后再通过扩展Reactive,创建一个可观察序列即可。
回到例子中的代码继续看textFiled.rx,同样是对UITextFiled转换成Reactive<UITextFiled>类型对象。调用text时调用了controlPropertyWithDefaultEvents
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
}
}
)
}
传入getter和setter闭包
getter是取出textfiled的text,setter是更新textfiled的text。
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)
}
.take(until: deallocated)
let bindingObserver = Binder(base, binding: setter)
return ControlProperty<T>(values: source, valueSink: bindingObserver)
}
这里封装了一个ControlProperty,绑定一个Binder。如果当前ControlProperty作为一个Observer,这个Binder才有用。
ControlTarget封装了UITextField的text改变的事件,通过callback通知调用者,然后通过observer.on(.next(getter(control)))
将当前UITextFiled的text值发出去。
我们进入ControlProperty结构体看看
public struct ControlProperty<PropertyType> : ControlPropertyType {
public typealias Element = PropertyType
let values: Observable<PropertyType>
let valueSink: AnyObserver<PropertyType>
/...
ControlProperty遵循了ControlPropertyType协议,而ControlPropertyType继承了ObservableType和ObserverType。所以既可以作为可观察序列又可以作为观察者,比如可以作为一个Binder。
作为可订阅序列,如例子中当我们订阅它时,一旦text改变,就可以接受到事件,订阅时执行的是values的订阅方法。订阅逻辑和第一章讲到的订阅逻辑一样,这里不再重述。
ControlProperty包含一个 values和一个valueSink,values是原始的observable,而且保证了在主线程执行subscribe handler。
values.subscribe(on: ConcurrentMainScheduler.instance)
valueSink是一个Binder,ControlProperty实现了on方法:
/// Binds event to user interface.
///
/// - In case next element is received, it is being set to control value.
/// - In case error is received, DEBUG buids raise fatal error, RELEASE builds log event to standard output.
/// - In case sequence completes, nothing happens.
public func on(_ event: Event<Element>) {
switch event {
case .error(let error):
bindingError(error)
case .next:
self.valueSink.on(event)
case .completed:
self.valueSink.on(event)
}
}
如果将ControlProperty作为一个Observer来使用,这里的on方法会接受到事件,同时将next和completed事件转发给valueSink进行处理,valueSink是在初始化ControlProperty赋值的:
let bindingObserver = Binder(base, binding: setter)
return ControlProperty<T>(values: source, valueSink: bindingObserver)
将setter闭包赋值给binding,setter就是最开始创建序列时定义的,用来设置UITextFiled的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
}
}
所以我们进入Binder类看一看:
/// Initializes `Binder`
///
/// - parameter target: Target object.
/// - parameter scheduler: Scheduler used to bind the events.
/// - parameter binding: Binding logic.
public init<Target: AnyObject>(_ target: Target, scheduler: ImmediateSchedulerType = MainScheduler(), binding: @escaping (Target, Value) -> Void) {
weak var weakTarget = target
self.binding = { event in
switch event {
case .next(let element):
_ = scheduler.schedule(element) { element in
if let target = weakTarget {
binding(target, element)
}
return Disposables.create()
}
case .error(let error):
rxFatalErrorInDebug("Binding error: \(error)")
case .completed:
break
}
}
}
/// Binds next element to owner view as described in `binding`.
public func on(_ event: Event<Value>) {
self.binding(event)
}
当调用valueSink的on方法时,会调用binding方法,也就是执行setter方法,完成UITextFiled 的text更新,而且为了保证更新操作再主线程执行,上面代码中默认的调度器是MainScheduler。
下面是一个例子,演示一个UITextField绑定另一个UITextField:
// 两个rx.text 返回的都是ControlProperty对象
let binder = textFiled2.rx.text
textFiled.rx.text.bind(to: binder)
.disposed(by: bag)
UITextFiled 也可以绑定attributedText;代码实现与绑定text类似。
Binder any thing
除了可以绑定UITextFiled,我们可以封装任意属性为一个Binder,比如lable.rx.text, 只需要在对象后面调用.rx.属性名, 这样就转换成一个Binder. 如:
textFiled.rx.text.bind(to: label.rx.text)
.disposed(by: bag)
这样text一旦发生改变,就会降其值赋值给label.text, 这就是绑定。我们看看label.rx.text方法实现
/// Automatically synthesized binder for a key path between the reactive
/// base and one of its properties
public subscript<Property>(dynamicMember keyPath: ReferenceWritableKeyPath<Base, Property>) -> Binder<Property> where Base: AnyObject {
Binder(self.base) { base, value in
base[keyPath: keyPath] = value
}
}
这里利用swift的keypath实现了任意属性的绑定,代码非常简洁巧妙。
然后再看看bind方法实现:
public func bind<Observer: ObserverType>(to observers: Observer...) -> Disposable where Observer.Element == Element {
self.subscribe { event in
observers.forEach { $0.on(event) }
}
}
实际上就是调用订阅方法,而且可以绑定多个Observer。
总结
- ControlProperty 和 ControlEvent都是Observable的变体,它们是对Observable的封装,封装了UI事件,将UI事件封装可观察序列。另外ControlProperty可以作为观察者被绑定。
- Binder是一个观察者,用来和可观察序列进行绑定,绑定实际上是对订阅操作的简化。