今天這篇是延續昨天的Reactive Extensions,在iOS設計中,delegate pattern很常見,但要套用到functional programming,似乎有點不知道串,所以,我們今天就來看看,大神們是怎麼將CLLocationManager封裝成Rx的樣子,官方程式在RxSwift/RxCLLocationManagerDelegateProxy.swift和CLLocationManager+Rx.swift
原本我們做delegate時,只能給一個人,但封裝之後,delegate被佔據,我們還是可以使用setDelegate跟給Rx Observable使用,這是因為有DelegateProxy,顧名思義是代理Delegate。
下方流程圖被記錄在DelegateProxyType.swift
當中,UIScrollView的delegate交給proxy,再由proxy去實現delegate內容,所以對外來說,只能接觸到proxy。
// DelegateProxyType.swift
+-------------------------------------------+
| |
| UIView subclass (UIScrollView) |
| |
+-----------+-------------------------------+
|
| Delegate
|
|
+-----------v-------------------------------+
| |
| Delegate proxy : DelegateProxyType +-----+----> Observable<T1>
| , UIScrollViewDelegate | |
+-----------+-------------------------------+ +----> Observable<T2>
| |
| +----> Observable<T3>
| |
| forwards events |
| to custom delegate |
| v
+-----------v-------------------------------+
| |
| Custom delegate (UIScrollViewDelegate) |
| |
+-------------------------------------------+
第一步,要為CLLocationManager建立DelegateProxy,需要繼承DelegateProxy<CLLocationManager, CLLocationManagerDelegate>
,實作DelegateProxyType
跟CLLocationManagerDelegate
RxCLLocationManagerDelegateProxy
: DelegateProxy<CLLocationManager, CLLocationManagerDelegate>
, DelegateProxyType
, CLLocationManagerDelegate {
第二步,完成init()
部分
public init(locationManager: CLLocationManager) {
super.init(parentObject: locationManager, delegateProxy: RxCLLocationManagerDelegateProxy.self)
}
這部分被定義在DelegateProxy.swift當中
public init<Proxy: DelegateProxyType>(parentObject: ParentObject, delegateProxy: Proxy.Type)
where Proxy: DelegateProxy<ParentObject, Delegate>, Proxy.ParentObject == ParentObject, Proxy.Delegate == Delegate {
…
}
第三步,CLLocationManager需要實現HasDelegate
protocol,並將Delegate
型態具體採用為CLLocationManagerDelegate
extension CLLocationManager: HasDelegate {
public typealias Delegate = CLLocationManagerDelegate
}
如此一來DelegateProxyType
就可以set, get Delegate
extension DelegateProxyType where ParentObject: HasDelegate, Self.Delegate == ParentObject.Delegate {
public static func currentDelegate(for object: ParentObject) -> Delegate? {
return object.delegate
}
public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) {
object.delegate = delegate
}
}
第四步
呼叫DelegateProxyType中的register<Parent>
,將RxCLLocationManagerDelegateProxy的instance儲存到DelegateProxyFactory
public static func registerKnownImplementations() {
self.register { RxCLLocationManagerDelegateProxy(locationManager: $0) }
}
(使用起來很簡單,但看這邊的Source Code是真的看不太懂...)
第四步已經完成DelegateProxy的建立,已經可以進入到下一段,對CLLocationManager進行Rx extension。
第五步
對CLLocationManager進行Rx extension,並透過proxy
來取得RxCLLocationManagerDelegateProxy
,在文件中明確提醒,絕對不要直接初始化RxCLLocationManagerDelegateProxy,而是要用proxy
來取得。
extension Reactive where Base: CLLocationManager {
public var delegate: DelegateProxy<CLLocationManager, CLLocationManagerDelegate> {
RxCLLocationManagerDelegateProxy.proxy(for: base)
}
}
第六步
現在就可以針對每一個delegate方法進行Rx化,將delegate的方法轉換成Observable,實際做法有兩種,第一種是在RxCLLocationManagerDelegateProxy.swift
所看到的,實現CLLocationManagerDelegate
方法,並以Subject作傳遞
internal lazy var didUpdateLocationsSubject = PublishSubject<[CLLocation]>()
internal lazy var didFailWithErrorSubject = PublishSubject<Error>()
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
_forwardToDelegate?.locationManager?(manager, didUpdateLocations: locations)
didUpdateLocationsSubject.onNext(locations)
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
_forwardToDelegate?.locationManager?(manager, didFailWithError: error)
didFailWithErrorSubject.onNext(error)
}
實際使用起來像這樣
public var didUpdateLocations: Observable<[CLLocation]> {
RxCLLocationManagerDelegateProxy.proxy(for: base).didUpdateLocationsSubject.asObservable()
}
另一種方式,使用methodInvoked
來取的Observable,a
即是[Any]
型態,表示著didUpdateLocations
的參數,以這裡來說,a[1]
就代表locations: [CLLocation]
public var didUpdateLocations: Observable<[CLLocation]> {
return delegate.methodInvoked(#selector(CLLocationManagerDelegate.locationManager(_:didUpdateLocations:)))
.map { a in
return try castOrThrow([CLLocation].self, a[1])
}
}
透過castOrThrow
將a[1]
轉成[CLLocation]
型態,若不成功則拋出錯誤
private func castOrThrow<T>(_ resultType: T.Type, _ object: Any) throws -> T {
guard let returnValue = object as? T else {
throw RxCocoaError.castingError(object: object, targetType: resultType)
}
return returnValue
}
第七步
原本使用起來會像這樣
extension LocationDemoViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
viewModel.location.onNext(locations.last!)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
viewModel.currentAuthorizationStatus.onNext(status)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
viewModel.error.onNext(error)
}
}
經過Rx extension後,實際使用起來會像下面這樣,是不是更符合Rx了呢!
func bindViewModel() {
viewModel.locationSignal.bind(to: infoLabel.rx.text).disposed(by: disposeBag)
locationManager.rx.didUpdateLocations.map { $0.last! }.bind(to: viewModel.location).disposed(by: disposeBag)
locationManager.rx.didChangeAuthorizationStatus.bind(to: viewModel.currentAuthorizationStatus).disposed(by: disposeBag)
locationManager.rx.didFailWithError.bind(to: viewModel.error).disposed(by: disposeBag)
}
到此為止,對Delegate Rx化已經講完了,在這次研究DelegateProxy過程中,參考了許多文章,其中有一篇講得特別清楚,受到許多幫助,也分享給大家,RxSwift: How to make your favorite Delegate-based-APIs Reactive ?
在看Source code的過程中,道行不夠,遇到許多困難,相對也學習到很多,大神們把程式變得很抽象,讓任何人都可以擴充自己的Rx extension,真的厲害的跟鬼一樣,大概就是這樣,明天開始,剩下時間會探索一些常用的library,掰掰