RxSwift進階:嘗試為自定義代理方法添加Reactive擴展

本文內(nèi)容架構(gòu):
  • itemSelected的底層實現(xiàn)
  • 實戰(zhàn)
tableView.rx.itemSelected
      .subscribe(onNext: { indexPath in
          // Todo
      })
      .disposed(by: disposeBag)

我們在使用RxSwift的時候經(jīng)常會遇到這樣的代碼囱井,類似的還有諸如itemDeselecteditemMoved六剥、itemInserted晚顷、itemDeleted等,它們都是對UITableView代理方法進行的一層Rx封裝疗疟。這樣做能讓我們避免因直接使用代理而不得不去做一些繁雜的工作该默,比如我們得去遵守不同的代理并且要實現(xiàn)相應(yīng)的代理方法等。

而將代理方法進行Rx化策彤,不僅會減少我們不必要的工作量栓袖,而且會使得代碼的聚合度更高,更加符合函數(shù)式編程的規(guī)范店诗。而在RxCocoa中我們也可以看到它為標準的Cocoa也同樣做了大量的封裝裹刮。

那么我們?nèi)绾螢樽约旱拇矸椒ㄌ砑覴eactive擴展呢?

我們先從tableView.rx.itemSelected的底層實現(xiàn)中探個究竟吧庞瘸。

一. itemSelected的底層實現(xiàn):


extension Reactive where Base: UITableView {
    // events

    /**
    Reactive wrapper for `delegate` message `tableView:didSelectRowAtIndexPath:`.
    */
    public var itemSelected: ControlEvent<IndexPath> {
        let source = self.delegate.methodInvoked(#selector(UITableViewDelegate.tableView(_:didSelectRowAt:)))
            .map { a in
                return try castOrThrow(IndexPath.self, a[1])
            }

        return ControlEvent(events: source)
    }

}
這里我們可以猜測其實現(xiàn)的大致流程是: 通過self.delegate觸發(fā)UITableViewDelegate.tableView(_:didDeselectRowAt:)方法(即通過代理調(diào)用代理的代理方法捧弃。有點拗口,后面再理解),并通過map函數(shù)把代理方法中的IndexPath參數(shù)包裝成事件流傳出违霞,供外部訂閱嘴办。

再來看看其中類型:

  • self: Reactive<Base>
    extension Reactive where Base: UITableView,可以理解成為Base后面的UITableView添加Rx擴展买鸽。
public struct Reactive<Base> {
    /// Base object to extend.
    public let base: Base

    /// Creates extensions with base object.
    ///
    /// - parameter base: Base object.
    public init(_ base: Base) {
        self.base = base
    }
}
  • delegate: DelegateProxy
    代理委托涧郊,即"代理的代理"。從這里可以回想一下上文的一句話"通過代理調(diào)用代理的代理方法"癞谒,那么更為準確的說底燎,"通過為"base"設(shè)計一個代理委托,當"base"的某個代理方法觸發(fā)時弹砚,其代理委托會做出相應(yīng)的響應(yīng)"

我們來瞧瞧UITableView的代理委托:

/// 奇了怪了双仍,該delegate怎么在UIScrollView的Reactive擴展里面。雖然UITableView繼承自UIScrollView
但UIScrollView的代理委托怎么就能響應(yīng)UITableViewDelegate的方法了桌吃? 別著急朱沃,下文給出了答案厅须。

extension Reactive where Base: UIScrollView {
        /// Reactive wrapper for `delegate`.
        ///
        /// For more information take a look at `DelegateProxyType` protocol documentation.
        public var delegate: DelegateProxy {
            return RxScrollViewDelegateProxy.proxyForObject(base)
        }
}

那么是不是我們照葫蘆畫瓢地為自己的代理設(shè)計一個像這樣的代理委托矢劲,就ok了呢叹誉?

暫不過早的下定論坚嗜,先來瞧瞧上面提到的 DelegateProxyType,其文檔解釋有點長陡舅,但每個單詞都很重要纵潦,且看且珍惜偶房。

..and because views that have delegates can be hierarchical

UITableView : UIScrollView : UIView

.. and corresponding delegates are also hierarchical

UITableViewDelegate : UIScrollViewDelegate : NSObject

.. and sometimes there can be only one proxy/delegate registered,
every view has a corresponding delegate virtual factory method.

這段話解釋了上文提到的delegate寫在UIScrollView的Reactive擴展的疑惑摆寄。因為view和它們的delegate的響應(yīng)都可以被繼承下來失暴。

DelegateProxyType.png
這是DelegateProxyType里的流程圖,那么這個圖說明了什么呢微饥?

以UIScrollView為例逗扒,Delegate proxy是其代理委托,遵守DelegateProxyType與UIScrollViewDelegate欠橘,并能響應(yīng)UIScrollViewDelegate的代理方法矩肩,這里我們可以為代理委托設(shè)計它所要響應(yīng)的方法(即設(shè)計暴露給訂閱者訂閱的信號量)。(----代理轉(zhuǎn)發(fā)機制)

到此肃续,一切瞬間變得清晰起來了有木有黍檩?照葫蘆畫瓢設(shè)計代理委托真的就ok呀!

二. 實戰(zhàn)

下面試著做一個簡單的demo來驗證一下吧!

我們首先來設(shè)計一個簡(zhuo)單(lue)的使用場景:創(chuàng)建一個TouchView類繼承自UIView并遵守TouchPointDelegate協(xié)議痹升,下面是協(xié)議里面的一個代理方法建炫,該代理方法的作用是返回所點擊view上的point。

@objc protocol TouchPointDelegate: NSObjectProtocol {
    @objc optional func touch(at point: CGPoint, in view: UIView)
}

我們現(xiàn)在要為上述的代理方法添加Rx擴展疼蛾,即當我們要使用該代理方法時可以像這樣優(yōu)雅的編碼:

touchView.rx.touchPoint
            .subscribe(onNext: { point in
                print(point)
            })
            .disposed(by: disposeBag)
首先我們來設(shè)計TouchView的代理委托RxTouchViewDelegateProxy

要設(shè)計DelegateProxy(代理委托),我們先來看一眼RxScrollViewDelegateProxy是如何定義的艺配,且要遵守哪些協(xié)議察郁。

public class RxScrollViewDelegateProxy
: DelegateProxy,
UIScrollViewDelegate, 
DelegateProxyType {}

現(xiàn)在照葫蘆畫瓢衍慎,
定義代理委托RxTouchViewDelegateProxy并遵守相應(yīng)的協(xié)議DelegateProxyDelegateProxyType皮钠,TouchPointDelegate稳捆。

class RxTouchViewDelegateProxy
: DelegateProxy, 
DelegateProxyType, 
TouchPointDelegate {}

此時會報RxTouchViewDelegateProxy沒有實現(xiàn)DelegateProxyType協(xié)議方法的警告。

那么麦轰,我們該實現(xiàn)哪些協(xié)議方法呢乔夯?

不知道您有沒有仔細閱讀DelegateProxyType,里面有兩個方法的解釋中都出現(xiàn)了該語句Each delegate property needs to have it's own type implementing "DelegateProxyType"款侵,我們嘗試實現(xiàn)這兩個方法末荐。

class RxTouchViewDelegateProxy: DelegateProxy, DelegateProxyType, TouchPointDelegate {
    static func currentDelegateFor(_ object: AnyObject) -> AnyObject? {
        let touchView: TouchView = castOrFatalError(object)
        return touchView.touchDelegate
    }
    
    static func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject) {
        let touchView: TouchView = castOrFatalError(object)
        touchView.touchDelegate = castOptionalOrFatalError(delegate)
    }
}

哎!現(xiàn)在終于看不到煩人的小紅點了新锈。

代理委托設(shè)計完成了甲脏,接下來就是為TouchView關(guān)聯(lián)設(shè)計好的代理委托。同樣的妹笆,可以先看一眼UIScrollView代理委托的關(guān)聯(lián)块请。

extension Reactive where Base: UIScrollView {
        public var delegate: DelegateProxy {
            return RxScrollViewDelegateProxy.proxyForObject(base)
        }
}

再次照葫蘆畫瓢。

extension Reactive where Base: TouchView {
    var touchDelegate: DelegateProxy {
        /// RxTouchViewDelegateProxy.proxyForObject(AnyObject): 設(shè)置代理委托實例
        return RxTouchViewDelegateProxy.proxyForObject(base)
    }
}

最后拳缠,為代理委托設(shè)計它所要響應(yīng)的方法(即暴露給訂閱者訂閱的信號量)

extension Reactive where Base: TouchView {
    // events
    
    var touchPoint: ControlEvent<CGPoint> {
        let source: Observable<CGPoint> = self.touchDelegate.methodInvoked(#selector(TouchPointDelegate.touch(at:in:)))
            .map({ a in
                return try castOrThrow(CGPoint.self, a[0])
            })
        return ControlEvent(events: source)
    }
}

因為swift編譯器的bug墩新,導致我們無法直接使用RxCocoa編寫好的強轉(zhuǎn)異常的處理函數(shù),所以這里需要我們手動去拷貝這部分代碼窟坐。當然啦海渊,也可以使用可選形式的方式進行處理。如下狸涌。

    static func currentDelegateFor(_ object: AnyObject) -> AnyObject? {
        let touchView: TouchView = (object as? TouchView)!
        return touchView.touchDelegate
    }
    
    static func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject) {
        let touchView: TouchView = (object as? TouchView)!
        touchView.touchDelegate = delegate as? TouchPointDelegate
    }

到此我們已經(jīng)成功為自定義的代理方法添加了Rx擴展切省。

? + R BINGO!
結(jié)語

因水平有限,對Rx文檔的解讀難免有疏漏帕胆,請閱讀本文的同時查看對應(yīng)的文檔內(nèi)容朝捆,如有不當,請多多賜教懒豹,小生在這兒謝過啦~

本文demo: demo
啟發(fā)文: RxCocoa 源碼解析--代理轉(zhuǎn)發(fā)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末芙盘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子脸秽,更是在濱河造成了極大的恐慌儒老,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件记餐,死亡現(xiàn)場離奇詭異驮樊,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門囚衔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挖腰,“玉大人,你說我怎么就攤上這事练湿『锫兀” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵肥哎,是天一觀的道長辽俗。 經(jīng)常有香客問我,道長篡诽,這世上最難降的妖魔是什么崖飘? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮霞捡,結(jié)果婚禮上坐漏,老公的妹妹穿的比我還像新娘。我一直安慰自己碧信,他們只是感情好赊琳,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著砰碴,像睡著了一般躏筏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呈枉,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天趁尼,我揣著相機與錄音,去河邊找鬼猖辫。 笑死酥泞,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的啃憎。 我是一名探鬼主播芝囤,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辛萍!你這毒婦竟也來了悯姊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤贩毕,失蹤者是張志新(化名)和其女友劉穎悯许,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辉阶,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡先壕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年瘩扼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片启上。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡邢隧,死狀恐怖店印,靈堂內(nèi)的尸體忽然破棺而出冈在,到底是詐尸還是另有隱情,我是刑警寧澤按摘,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布包券,位于F島的核電站,受9級特大地震影響炫贤,放射性物質(zhì)發(fā)生泄漏溅固。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一兰珍、第九天 我趴在偏房一處隱蔽的房頂上張望侍郭。 院中可真熱鬧,春花似錦掠河、人聲如沸亮元。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爆捞。三九已至,卻和暖如春勾拉,著一層夾襖步出監(jiān)牢的瞬間煮甥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工藕赞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留成肘,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓斧蜕,卻偏偏與公主長得像双霍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子惩激,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理店煞,服務(wù)發(fā)現(xiàn),斷路器风钻,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • RxSwift_v1.0筆記——13 Intermediate RxCocoa 這章將學習一些高級的RxCocoa...
    大灰很閱讀 658評論 1 1
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,139評論 30 470
  • 我與你的生活 像風一樣自由 陽光透過青苔的瓦檐 柔和細膩 我揚起臉龐 透過炊煙 看見湛藍劃破天際 天上的云朵尋覓 ...
    清風浦上閱讀 142評論 0 1
  • 昨天上午三次顷蟀,下午一次,吐過之后的虛弱不堪骡技,艾瑪鸣个,我真怕了羞反。 今天正兒八經(jīng)的只吐了晚上這一回狠的,早上中午下午的都...
    我是薔薇閱讀 136評論 0 0