RxSwift進(jìn)階:嘗試為自定義代理方法添加Reactive擴(kuò)展

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

我們?cè)谑褂肦xSwift的時(shí)候經(jīng)常會(huì)遇到這樣的代碼,類(lèi)似的還有諸如itemDeselected、itemMoveditemInserted、itemDeleted等,它們都是對(duì)UITableView代理方法進(jìn)行的一層Rx封裝。這樣做能讓我們避免因直接使用代理而不得不去做一些繁雜的工作,比如我們得去遵守不同的代理并且要實(shí)現(xiàn)相應(yīng)的代理方法等。

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

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

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

一. itemSelected的底層實(shí)現(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)
    }

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

再來(lái)看看其中類(lèi)型:

  • self: Reactive<Base>
    extension Reactive where Base: UITableView,可以理解成為Base后面的UITableView添加Rx擴(kuò)展。
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
    代理委托,即"代理的代理"。從這里可以回想一下上文的一句話"通過(guò)代理調(diào)用代理的代理方法",那么更為準(zhǔn)確的說(shuō),"通過(guò)為"base"設(shè)計(jì)一個(gè)代理委托,當(dāng)"base"的某個(gè)代理方法觸發(fā)時(shí),其代理委托會(huì)做出相應(yīng)的響應(yīng)"

我們來(lái)瞧瞧UITableView的代理委托:

/// 奇了怪了,該delegate怎么在UIScrollView的Reactive擴(kuò)展里面。雖然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)
        }
}

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

暫不過(guò)早的下定論,先來(lái)瞧瞧上面提到的 DelegateProxyType,其文檔解釋有點(diǎn)長(zhǎng),但每個(gè)單詞都很重要,且看且珍惜。

..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寫(xiě)在UIScrollView的Reactive擴(kuò)展的疑惑。因?yàn)関iew和它們的delegate的響應(yīng)都可以被繼承下來(lái)。

DelegateProxyType.png
這是DelegateProxyType里的流程圖,那么這個(gè)圖說(shuō)明了什么呢?

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

到此,一切瞬間變得清晰起來(lái)了有木有?照葫蘆畫(huà)瓢設(shè)計(jì)代理委托真的就ok呀!

二. 實(shí)戰(zhàn)

下面試著做一個(gè)簡(jiǎn)單的demo來(lái)驗(yàn)證一下吧!

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

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

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

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

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

public class RxScrollViewDelegateProxy
: DelegateProxy,
UIScrollViewDelegate, 
DelegateProxyType {}

現(xiàn)在照葫蘆畫(huà)瓢,
定義代理委托RxTouchViewDelegateProxy并遵守相應(yīng)的協(xié)議DelegateProxy,DelegateProxyType,TouchPointDelegate

class RxTouchViewDelegateProxy
: DelegateProxy, 
DelegateProxyType, 
TouchPointDelegate {}

此時(shí)會(huì)報(bào)RxTouchViewDelegateProxy沒(méi)有實(shí)現(xiàn)DelegateProxyType協(xié)議方法的警告。

那么,我們?cè)搶?shí)現(xiàn)哪些協(xié)議方法呢?

不知道您有沒(méi)有仔細(xì)閱讀DelegateProxyType,里面有兩個(gè)方法的解釋中都出現(xiàn)了該語(yǔ)句Each delegate property needs to have it's own type implementing "DelegateProxyType",我們嘗試實(shí)現(xiàn)這兩個(gè)方法。

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)在終于看不到煩人的小紅點(diǎn)了。

代理委托設(shè)計(jì)完成了,接下來(lái)就是為T(mén)ouchView關(guān)聯(lián)設(shè)計(jì)好的代理委托。同樣的,可以先看一眼UIScrollView代理委托的關(guān)聯(lián)。

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

再次照葫蘆畫(huà)瓢。

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

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

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)
    }
}

因?yàn)閟wift編譯器的bug,導(dǎo)致我們無(wú)法直接使用RxCocoa編寫(xiě)好的強(qiáng)轉(zhuǎn)異常的處理函數(shù),所以這里需要我們手動(dòng)去拷貝這部分代碼。當(dāng)然啦,也可以使用可選形式的方式進(jìn)行處理。如下。

    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擴(kuò)展。

? + R BINGO!
結(jié)語(yǔ)

因水平有限,對(duì)Rx文檔的解讀難免有疏漏,請(qǐng)閱讀本文的同時(shí)查看對(duì)應(yīng)的文檔內(nèi)容,如有不當(dāng),請(qǐng)多多賜教,小生在這兒謝過(guò)啦~

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容