RxSwift源碼探秘 :如何讓UIControl生成可觀察序列

我們在使用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可觀察序列的。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咳榜,一起剝皮案震驚了整個濱河市夏醉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涌韩,老刑警劉巖畔柔,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異臣樱,居然都是意外死亡靶擦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門雇毫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玄捕,“玉大人,你說我怎么就攤上這事棚放∶墩常” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵飘蚯,是天一觀的道長馍迄。 經(jīng)常有香客問我,道長局骤,這世上最難降的妖魔是什么柬姚? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮庄涡,結果婚禮上量承,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好撕捍,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布拿穴。 她就那樣靜靜地躺著,像睡著了一般忧风。 火紅的嫁衣襯著肌膚如雪默色。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天狮腿,我揣著相機與錄音腿宰,去河邊找鬼。 笑死缘厢,一個胖子當著我的面吹牛吃度,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贴硫,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼椿每,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了英遭?” 一聲冷哼從身側響起间护,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挖诸,沒想到半個月后汁尺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡多律,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年痴突,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菱涤。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖洛勉,靈堂內(nèi)的尸體忽然破棺而出粘秆,到底是詐尸還是另有隱情,我是刑警寧澤收毫,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布攻走,位于F島的核電站,受9級特大地震影響此再,放射性物質(zhì)發(fā)生泄漏昔搂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一输拇、第九天 我趴在偏房一處隱蔽的房頂上張望摘符。 院中可真熱鬧,春花似錦、人聲如沸逛裤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽带族。三九已至锁荔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蝙砌,已是汗流浹背阳堕。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留择克,地道東北人恬总。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像祠饺,于是被迫代替她去往敵國和親越驻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 最近在學習RxSwift相關的內(nèi)容道偷,在這里記錄一些基本的知識點缀旁,以便今后查閱。 Observable 在RxSwi...
    L_Zephyr閱讀 1,762評論 1 4
  • 本章將向你介紹另一個框架勺鸦,它是原生RxSwift庫的一部分:RxCocoa并巍。 RxCocoa全平臺通用。每個平臺有...
    大灰很閱讀 874評論 3 2
  • 作者: maplejaw本篇只解析標準包中的操作符换途。對于擴展包懊渡,由于使用率較低,如有需求军拟,請讀者自行查閱文檔剃执。 創(chuàng)...
    maplejaw_閱讀 45,697評論 8 93
  • 歲月慢慢磨平了棱角,已經(jīng)過了飛蛾的年紀懈息,那就好好的做只候鳥肾档。只愿此生淡然!越簡單越純粹辫继。
    困了可以想睡就睡么閱讀 232評論 0 0
  • 這個framework級別的動畫效果有以下特點: Group-level animations在一個View Hi...
    lxacoder閱讀 764評論 0 1