本文內(nèi)容架構(gòu):
itemSelected
的底層實現(xiàn)- 實戰(zhàn)
tableView.rx.itemSelected
.subscribe(onNext: { indexPath in
// Todo
})
.disposed(by: disposeBag)
我們在使用RxSwift的時候經(jīng)常會遇到這樣的代碼囱井,類似的還有諸如itemDeselected
、itemMoved
六剥、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里的流程圖,那么這個圖說明了什么呢微饥?
以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é)議DelegateProxy
,DelegateProxyType
皮钠,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ā)