我們在使用RxSwift進行響應式編程時丧肴,都會引入另一個庫RxCocoa,RxCocoa是Rx對iOS的原生API中UIKit以及Foundation中的視圖(UIView)、控制事件(Control Event)、鍵值觀察(KVO)碾阁、通知(Notification)等的擴展,以便在開發(fā)時更方便的對系統(tǒng)的這些原生組件進行Rx應用些楣。例如:
//registerButton是一個UIButton脂凶,通過.rx.tap能獲取到該button的點擊事件序列
_ = registerButton.rx.tap.shareReplay(1).subscribe {(_) in
//訂閱者獲取到按鈕點擊的事件,再做處理
}.disposed(by: disposeBag)
//nameTextField是一個UITextField控件愁茁,可以直接通過.rx.text獲取到該控件中輸入內(nèi)容的String序列
_ = nameTextField.rx.text.shareReplay(1).subscribe {(text) in
//訂閱者實時獲取到text內(nèi)容蚕钦,再做處理
}.disposed(by: disposeBag)
上述例子中通過registerButton.rx.tap,我們可以獲取該button的點擊事件序列鹅很,而nameTextField.rx.text給我們提供了UITextField中的文本String的序列嘶居,這就像我們通過正常的編碼方式獲取到的一樣:
//給registerButton添加Target和點擊事件
registerButton.addTarget(self, action: #selector(registerButtonClick), for: .touchUpInside)
@objc func registerButtonClick(){
//注冊按鈕點擊了
}
//nameTextField通過通知觀察獲得UITextField變化的文本
NotificationCenter.default.addObserver(self, selector: #selector(registerButtonClick), name: NSNotification.Name.UITextFieldTextDidChange, object: nil)
@objc func nameTextFieldValueChanged(){
//獲取到UITextField的text
}
對比兩者不難發(fā)現(xiàn),系統(tǒng)原生的方法有種割裂感,而RxCocoa提供的被觀察者以及訂閱的模式更符合我們正常思考的模式邮屁,在事件發(fā)生變化后及時作出回應整袁,而這正是RxSwift響應式編程的核心思想。要達到這種響應式編程的前提條件就是先得有一個可被觀察的序列Observable佑吝,而RxCocoa中又是怎么將UIButton和UITextField這些繼承于UIControl的控件的控制事件轉化為一個個不同的Observable事件序列的呢坐昙,本篇文章就以UIButton和UITextField為例,探尋RxCocoa源碼中的奧秘芋忿。
Observable的創(chuàng)建
RxSwift中炸客,在Observable的擴展類里提供了一系列創(chuàng)建被觀察對象的工廠化方法。例如通過never戈钢、empty痹仙、just、of等來創(chuàng)建不同的Observable序列殉了,例如:
Observable.just("??")
.subscribe { event in
print(event)
}
.disposed(by: disposeBag)
Observable.of("??", "??", "??", "??")
.subscribe(onNext: { element in
print(element)
})
.disposed(by: disposeBag)
但如何將一個UIControl的Control Event(包括將來即將發(fā)生的點擊事件开仰、text變化等等)也轉換成對應的序列呢,以上方法好像并不適用薪铜,這時就需要用到自定義創(chuàng)建Observable方法了抖所,來看看官方示例:
let myJust = { (element: String) -> Observable<String> in
return Observable.create { observer in
observer.on(.next(element))
observer.on(.completed)
return Disposables.create()
}
}
myJust("??").subscribe {
print($0)
}.disposed(by: disposeBag)
通過Observable.create方法可以得到一個Observable<E>序列,E是一個泛型類型痕囱,在上述例子中E就是String類型
public static func create(_ subscribe: @escaping (AnyObserver<E>) -> Disposable) -> Observable<E> {
return AnonymousObservable(subscribe)
}
而要想達到持續(xù)發(fā)送事件的目的,只要在@escaping (AnyObserver<E>) -> Disposable) 這個閉包中進行observer.on(.next(element))操作就可以將由element構成的序列發(fā)送給訂閱者了暴匠。
注意:此處的觀察者AnyObserver<E>的觀察類型E與你生成的被觀察者類型Observable<E>的E要確保一致鞍恢。而RxCocoa中正是在UIControl擴展中使用了自定義創(chuàng)建Observable.create方法來處理并轉化對應的事件。
UIControl+Rx
下面分別以UIButton和UITextField為例每窖,來探尋UIControl+Rx這個Reactive限定擴展中的秘密帮掉。(關于限定擴展可以看我上一篇文章)
先來看通過UIButton.rx.tap我們得到了一個什么東西:
extension Reactive where Base: UIButton {
/// Reactive wrapper for `TouchUpInside` control event.
public var tap: ControlEvent<Void> {
return controlEvent(.touchUpInside)
}
}
我們得到一個ControlEvent<Void>,而ControlEvent<Void>是什么呢
//ControlEvent<Void>遵循ControlEventType協(xié)議
public struct ControlEvent<PropertyType> : ControlEventType
//ControlEventType遵循ObservableType協(xié)議
public protocol ControlEventType : ObservableType
而我們知道,遵循ObservableType就可以當做一個Observable序列窒典,所以通過對UIButton的.rx.tap我們得到了一個不含任何數(shù)據(jù)(即Void)的事件序列蟆炊,再來進一步對源碼分析,此處返回的ControlEvent<Void>是通過controlEvent(.touchUpInside)得到的瀑志,來看controlEvent方法:
extension Reactive where Base: UIControl {
/// - parameter controlEvents: Filter for observed event types.
///此處傳入UIControlEvents控制事件類型涩搓,以上UIButton傳入的是.touchUpInside點擊事件
public func controlEvent(_ controlEvents: UIControlEvents) -> ControlEvent<Void> {
//通過Observable.create自定義創(chuàng)建一個Observable
let source: Observable<Void> = Observable.create { [weak control = self.base] observer in
MainScheduler.ensureExecutingOnScheduler()
guard let control = control else {
observer.on(.completed) // 如果self.base為空,則發(fā)送完成事件
return Disposables.create()
}
//通過控件control和事件controlEvents初始化一個ControlTarget類劈猪,
//在control觸發(fā)controlEvents事件時昧甘,通過閉包回調(diào)回來(詳見后面)
let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) { control in
observer.on(.next()) //一旦有回調(diào),就給訂閱者發(fā)送事件
}
return Disposables.create(with: controlTarget.dispose)
}.takeUntil(deallocated)
//將自定義序列source包裝成ControlEvent返回
return ControlEvent(events: source)
}
ControlTarget
以上源碼中最關鍵是ControlTarget這個初始化方法战得,通過控件control(UIButton充边、UITextField等等)和事件controlEvents初始化一個ControlTarget類,在control觸發(fā)controlEvents事件時常侦,通過閉包回調(diào)回來
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: UIControlEvents
#endif
var callback: Callback?
#if os(iOS) || os(tvOS)
//傳入控件control(UIButton浇冰、UITextField等等)和事件controlEvents
init(control: Control, controlEvents: UIControlEvents, callback: @escaping Callback) {
MainScheduler.ensureExecutingOnScheduler()
self.control = control
self.controlEvents = controlEvents
self.callback = callback //等待self.callback回調(diào)
super.init()
//將controlEvents事件添加到自己贬媒,響應selector方法
control.addTarget(self, action: selector, for: controlEvents)
let method = self.method(for: selector)
if method == nil {
rxFatalError("Can't find method")
}
}
該事件觸發(fā)的方法為#selector(ControlTarget.eventHandler(_:)),eventHandler方法是怎么處理的呢肘习,
let selector: Selector = #selector(ControlTarget.eventHandler(_:))
//控制事件controlEvents所需要觸發(fā)的方法
func eventHandler(_ sender: Control!) {
if let callback = self.callback, let control = self.control {
callback(control) //在self.control和callback都不為空時 回調(diào)
}
}
一目了然际乘,通過上面的callback(control) 我們就能在下面這個回調(diào)函數(shù)中給訂閱者發(fā)送事件了
let controlTarget = ControlTarget(control: control, controlEvents:controlEvents) { control in
observer.on(.next()) //一旦有回調(diào),就給訂閱者發(fā)送事件
}
同理井厌,我們再來看看UITextField.rx.text生成String可觀察序列的代碼:
extension Reactive where Base: UITextField {
/// Reactive wrapper for `text` property.
public var text: ControlProperty<String?> {
return value
}
/// Reactive wrapper for `text` property.
public var value: ControlProperty<String?> {
//UIControl.rx.value方法是關鍵
return UIControl.rx.value(
base,
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
}
}
)
}
}
接著看UIControl.rx.value這個方法的源碼:
//參數(shù)為控件本身蚓庭,其他分別為兩個閉包,
//其中getter是用來獲取UITextField的text值發(fā)送給訂閱者仅仆,setter是設置數(shù)值
static func value<C: UIControl, T>(_ control: C, getter: @escaping (C) -> T, setter: @escaping (C, T) -> Void) -> ControlProperty<T> {
let source: Observable<T> = Observable.create { [weak weakControl = control] observer in
guard let control = weakControl else {
observer.on(.completed)
return Disposables.create()
}
observer.on(.next(getter(control))) //訂閱后首次發(fā)送事件
//通過ControlTarget來監(jiān)聽UITextField所有跟編輯有關以及值修改的控制事件
let controlTarget = ControlTarget(control: control, controlEvents: [.allEditingEvents, .valueChanged]) { _ in
if let control = weakControl {
observer.on(.next(getter(control))) //一旦有變化就發(fā)送給訂閱者
}
}
return Disposables.create(with: controlTarget.dispose)
}
.takeUntil((control as NSObject).rx.deallocated)
let bindingObserver = UIBindingObserver(UIElement: control, binding: setter)
return ControlProperty<T>(values: source, valueSink: bindingObserver)
}
通過ControlTarget來監(jiān)聽UITextField所有跟編輯有關以及value值發(fā)生改變的控制事件器赞,最后生成一個ControlProperty類型的Observable可觀察序列,然后發(fā)送給訂閱者墓拜。
以上就是RxCocoa中如何通過自定義創(chuàng)建Observable港柜,將UIControl控件的控制事件轉化為Observable可觀察序列的。