RxCocoa(scrollView.rx.didScroll
)
得益于RxCocoa對UIKit做了extension,我們使用UI組件的Rx封裝時(shí)只需要調(diào)用rx
屬性烹看,就可以訪問到Rx框架的內(nèi)容刊头。比如需要訂閱UIScrollView的滾動事件:
scrollView
.rx.didScroll
.subscribe(onNext: { () in
print("scroll view did scroll")
})
But, why?眠寿??
在原生的Cocoa框架中躬柬,要監(jiān)聽UIScrollView的滑動事件,需要通過實(shí)現(xiàn)UIScrollViewDelegate協(xié)議的scrollViewDidScroll
函數(shù)抽减。但是RxCocoa為何可以讓用戶通過簡單的調(diào)用就得到了回調(diào)允青?UIScrollViewDelegate是如何被隱藏的?
rx
屬性是怎么來的?
Reactive&ReactiveCompatible
通過查看卵沉,rx
屬性是一個(gè)Reactive
的類型颠锉。在Reactive.swift文件法牲,可以看到它是一個(gè)泛型struct 。而rx
是在一個(gè)ReactiveCompatible
的protocol里面定義的琼掠。
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
}
}
/// A type that has reactive extensions.
public protocol ReactiveCompatible {
/// Extended type
associatedtype CompatibleType
/// Reactive extensions.
static var rx: Reactive<CompatibleType>.Type { get set }
/// Reactive extensions.
var rx: Reactive<CompatibleType> { get set }
}
extension ReactiveCompatible {
/// Reactive extensions.
public static var rx: Reactive<Self>.Type {
get {
return Reactive<Self>.self
}
set {
// this enables using Reactive to "mutate" base type
}
}
/// Reactive extensions.
public var rx: Reactive<Self> {
get {
return Reactive(self)
}
set {
// this enables using Reactive to "mutate" base object
}
}
}
import class Foundation.NSObject
/// Extend NSObject with `rx` proxy.
extension NSObject: ReactiveCompatible { }
可以看到Reactive
的定義非常簡單拒垃,只有一個(gè)base
屬性,而這個(gè)屬性是一個(gè)泛型瓷蛙。
在文件的最后一行悼瓮,NSObject被聲明了實(shí)現(xiàn)ReactiveCompatible
,因此UIScrollView也實(shí)現(xiàn)了該協(xié)議艰猬。
查看ReactiveCompatible
的定義以及其下方的extension就可以看出横堡,rx
屬性的泛型是該類型本身,也就是說UISCrollView的rx
屬性就是一個(gè)UISCrollView泛型的Reactive
結(jié)構(gòu)體冠桃,其base
屬性就是這個(gè)UIScrollView本身的實(shí)例**命贴。
順著往下看,可以在UIScrollView+Rx.swift文件里找到食听,是通過extension的where語法對UIScrollView泛型的Reactive
添加的一個(gè)計(jì)算型屬性胸蛛。
extension Reactive where Base: UIScrollView {
...
...
...
/// Reactive wrapper for delegate method `scrollViewDidScroll`
public var didScroll: ControlEvent<Void> {
let source = RxScrollViewDelegateProxy.proxy(for: base).contentOffsetPublishSubject
return ControlEvent(events: source)
}
...
...
...
}
從命名可以看出,RxScrollViewDelegateProxy
是一個(gè)實(shí)現(xiàn)UIScrollViewDelegate
的代理類型樱报。
暫時(shí)不探究RxScrollViewDelegateProxy
的proxy(for:)
函數(shù)胚泌,比較好理解,source
是通過一個(gè)對應(yīng)UISrollView實(shí)例獲得的RxScrollViewDelegateProxy
的一個(gè)UIScrollView的contentOffset事件廣播肃弟,函數(shù)最后再把這個(gè)廣播封裝成一個(gè)ControlEvent
類型實(shí)例玷室,也就是被訂閱的那個(gè)事件。
而關(guān)鍵就在于RxScrollViewDelegateProxy
這個(gè)類型做了什么事情笤受。
RxScrollViewDelegateProxy
繼續(xù)查看RxScrollViewDelegateProxy.swift文件
/// For more information take a look at `DelegateProxyType`.
open class RxScrollViewDelegateProxy
: DelegateProxy<UIScrollView, UIScrollViewDelegate>
, DelegateProxyType
, UIScrollViewDelegate {
...
...
...
fileprivate var _contentOffsetPublishSubject: PublishSubject<()>?
...
/// Optimized version used for observing content offset changes.
internal var contentOffsetPublishSubject: PublishSubject<()> {
if let subject = _contentOffsetPublishSubject {
return subject
}
let subject = PublishSubject<()>()
_contentOffsetPublishSubject = subject
return subject
}
...
...
...
}
RxScrollViewDelegateProxy
繼承了DelegateProxy
穷缤,并實(shí)現(xiàn)了兩個(gè)協(xié)議:
- DelegateProxyType
- UIScrollViewDelegate
顯然這里就是UIScrollViewDelegate的藏身點(diǎn),下面繼續(xù)看在這里它是如何被黑盒化的箩兽。
在這個(gè)文件里可以找到原始回調(diào)的函數(shù):
/// For more information take a look at `DelegateProxyType`.
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let subject = _contentOffsetBehaviorSubject {
subject.on(.next(scrollView.contentOffset))
}
if let subject = _contentOffsetPublishSubject {
subject.on(.next(()))
}
self._forwardToDelegate?.scrollViewDidScroll?(scrollView)
}
在回調(diào)的處理上津肛,做了3件事:
- 發(fā)送contentOffset的數(shù)值變化廣播
- 發(fā)送contentOffset變化的事件廣播
- 調(diào)用了
_forwardToDelegate
屬性的另一個(gè)scrollViewDidScroll函數(shù)
可以看到兩個(gè)廣播事件應(yīng)該是按需發(fā)送的,如果在沒有訂閱者的情況下汗贫,應(yīng)該不會產(chǎn)生對應(yīng)的屬性身坐。而在上面被訂閱的的contentOffsetPublishSubject
就是這個(gè)_contentOffsetPublishSubject
。
而_forwardToDelegate
應(yīng)該是開發(fā)者在外部設(shè)置的delegate落包,也就是說:
即使開發(fā)者已經(jīng)為一個(gè)UIScrollView設(shè)置了delegate(或者沒有設(shè)置)部蛇,也不會影響通過RxCocoa框架去訂閱這個(gè)UIScrollView的事件。 而且可以有多個(gè)訂閱者通過RxCocoa去訂閱UIScrollView的回調(diào)事件咐蝇,因?yàn)檫@里的Observable
是廣播類型涯鲁。**
可見在RxCocoa中,每一個(gè)有事件訂閱者的UIScrollView都有與之對應(yīng)的RxScrollViewDelegateProxy
實(shí)例。所以下一個(gè)問題就是:
代碼RxScrollViewDelegateProxy.proxy(for: base)
當(dāng)中抹腿,proxy(for:)
函數(shù)是如何把一個(gè)RxScrollViewDelegateProxy
綁定到一個(gè)UIScrollView上的岛请?
DelegateProxyType
可以查看到proxy(for:)
函數(shù)被定義在DelegateProxyType
協(xié)議里,通過extension實(shí)現(xiàn)警绩。
/// Returns existing proxy for object or installs new instance of delegate proxy.
///
/// - parameter object: Target object on which to install delegate proxy.
/// - returns: Installed instance of delegate proxy.
///
///
/// extension Reactive where Base: UISearchBar {
///
/// public var delegate: DelegateProxy<UISearchBar, UISearchBarDelegate> {
/// return RxSearchBarDelegateProxy.proxy(for: base)
/// }
///
/// public var text: ControlProperty<String> {
/// let source: Observable<String> = self.delegate.observe(#selector(UISearchBarDelegate.searchBar(_:textDidChange:)))
/// ...
/// }
/// }
public static func proxy(for object: ParentObject) -> Self {
MainScheduler.ensureExecutingOnScheduler()
let maybeProxy = self.assignedProxy(for: object)
let proxy: AnyObject
if let existingProxy = maybeProxy {
proxy = existingProxy
}
else {
proxy = castOrFatalError(self.createProxy(for: object))
self.assignProxy(proxy, toObject: object)
assert(self.assignedProxy(for: object) === proxy)
}
let currentDelegate = self._currentDelegate(for: object)
let delegateProxy: Self = castOrFatalError(proxy)
if currentDelegate !== delegateProxy {
delegateProxy._setForwardToDelegate(currentDelegate, retainDelegate: false)
assert(delegateProxy._forwardToDelegate() === currentDelegate)
self._setCurrentDelegate(proxy, to: object)
assert(self._currentDelegate(for: object) === proxy)
assert(delegateProxy._forwardToDelegate() === currentDelegate)
}
return delegateProxy
}
這個(gè)函數(shù)的參數(shù)只有1個(gè)ParentObject
類型崇败,其實(shí)在協(xié)議中定義了,就是一個(gè)AnyObject類型肩祥。
可以看到僚匆,這個(gè)操作需要確保在主線程上進(jìn)行,換句話說搭幻,開發(fā)者不可以在子線程上訂閱UIScrollView的回調(diào)事件咧擂。然后調(diào)用一個(gè)assignedProxy
函數(shù)獲取一個(gè)maybeProxy
結(jié)果。而這個(gè)函數(shù)做的事情檀蹋,就是通過runtime的objc_getAssociatedObject函數(shù)在ParentObject
上查詢是否有綁定的對象松申,使用的key是該DelegateProxyType
實(shí)例本身的identifier
變量。(此處有一個(gè)由Swift編譯器引起的bug俯逾,Rx團(tuán)隊(duì)需要對objc_getAssociatedObject的直接結(jié)果做一個(gè)處理才可以返回贸桶,暫時(shí)不探究。)
看到這里已經(jīng)可以看出:
RxCocoa可以讓開發(fā)者跳過實(shí)現(xiàn)Delegate函數(shù)直接獲取UIKit組件的回調(diào)桌肴,其實(shí)是通過runtime把一個(gè)已經(jīng)實(shí)現(xiàn)了Delegate的Proxy綁定到了這個(gè)組件上皇筛。
繼續(xù)看完這個(gè)函數(shù),如果maybeProxy
得出的結(jié)果是空的坠七,就會通過createProxy
創(chuàng)建一個(gè)新的Self
實(shí)例并綁定到UIScrollView
的實(shí)例上水醋。這里的Self
對應(yīng)的是那個(gè)實(shí)現(xiàn)DelegateProxyType
的類型,對應(yīng)上當(dāng)前場景的就是RxScrollViewDelegateProxy
彪置。并且在綁定完成后再做了一次判斷拄踪,確保對應(yīng)identifier
綁定的實(shí)例就是剛剛新創(chuàng)建出來的那個(gè)。(猜測:為了避免有別的線程也使用同樣的identifier綁定了其他實(shí)例拳魁?)
然后通過函數(shù)_currentDelegate(for:)
獲取當(dāng)前UIScrollView的delegate惶桐,這是一個(gè)抽象函數(shù),具體返回的"delegate"是根據(jù)傳入?yún)?shù)實(shí)現(xiàn)的協(xié)議來定潘懊,詳情可以在同一個(gè)文件下找到姚糊,分別有以下幾種情況:
ParentObject: HasDelegate, Self.Delegate == ParentObject.Delegate
ParentObject: HasDataSource, Self.Delegate == ParentObject.DataSource
ParentObject: HasPrefetchDataSource, Self.Delegate == ParentObject.PrefetchDataSource
目前場景下屬于情況1,所以看這個(gè)extension里面的實(shí)現(xiàn)授舟,實(shí)際上是返回了傳入?yún)?shù)的的delegate救恨,而在RxScrollViewDelegateProxy.swift文件里就可以看到有UIScrollView的HasDelegate
實(shí)現(xiàn),這個(gè)delegate其實(shí)就是UIScrollViewDelegate岂却。
拿到這個(gè)delegate后忿薇,會和上面的proxy做對比(在對比之前又做了一次對proxy的判斷裙椭,確認(rèn)其類型就是當(dāng)前需要的DelegateProxy)躏哩,然后做托管處理署浩。也就是說,如果在開發(fā)者設(shè)置訂閱UIScrollView之前扫尺,UIScrollView已經(jīng)有一個(gè)delegate筋栋,在這里就會把這個(gè)delegate托管給proxy,讓proxy在收到UIScrollView回調(diào)的時(shí)候轉(zhuǎn)發(fā)給delegate正驻,而實(shí)際上UIScrollView此時(shí)的delegate指向的是proxy弊攘。通過proxy的forwardToDelegate
可以找回這個(gè)在外部設(shè)置的delegate。**
持續(xù)更新...