本文內(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、itemMoved、itemInserted、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里的流程圖,那么這個(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ā)