天天看點

RxCocoa - DelegateProxy&DelegateProxyTypeDelegateProxyDelegateProxyType為什麼需要DelegateProxy&DelegateProxyType?Tableview的點選事件實作自定義代理實作

DelegateProxy

由英文意思就明白,委托代理,但是需要注意:該類的實作并不是安全的,目前隻能用于主線程。

DelegateProxyType

DelegateProxyType協定允許使用正常的代理和Rx可觀察者序列視圖,并且隻有唯一的delegate/datasource 被注冊。DelegateProxyType協定應該從未直接被初始化,為了擷取DelegateProxyType類型執行個體,應該使用proxyForObject方法。簡單的工作原理如下:

RxCocoa - DelegateProxy&DelegateProxyTypeDelegateProxyDelegateProxyType為什麼需要DelegateProxy&DelegateProxyType?Tableview的點選事件實作自定義代理實作

為了将 delegate 中每個邏輯分解出來,我們需要建立一個中間代理,每次觸發 delegate 時,都先觸發這個代理,然後這個代理作為一個序列發射值給多個訂閱者,這樣我們就将不相關的邏輯分開了,不需要将所有的邏輯都寫在同一個 delegate 中。事實上 RxCocoa 已經為我們提供了一些 proxy :

RxCocoa - DelegateProxy&DelegateProxyTypeDelegateProxyDelegateProxyType為什麼需要DelegateProxy&DelegateProxyType?Tableview的點選事件實作自定義代理實作

為什麼需要DelegateProxy&DelegateProxyType?

因為項目中使用了RxSwift,如果我們想進行鍊式操作,并且又涉及到使用代理,那麼使用代理的模式将并部适合我們的鍊式處理操作,是以RxCocoa為我們提供了DelegateProxy來處理代理,這樣能夠更好的進行響應式程式設計。

Tableview的點選事件實作

/**
     Reactive wrapper for `delegate` message `tableView:didDeselectRowAtIndexPath:`.
     */
    public var itemDeselected: ControlEvent<IndexPath> {
        let source = self.delegate.methodInvoked(#selector(UITableViewDelegate.tableView(_:didDeselectRowAt:)))
            .map { a in
                return try castOrThrow(IndexPath.self, a[1])
            }

        return ControlEvent(events: source)
    }
           

它實際上是使用了methodInvoked方法,該方法傳入對應的代理方法,每當代理方法被調用,會觸發發送對應的事件,然後封裝為對應的ControlEvent。是以每次我們點選cell,會觸發對應的代理方法,進而觸發觀察者序列發送事件,并且攜帶對應的位置作為參數,隻要我們使用該屬性進行訂閱,我們就可以擷取對應的點選位置。是以,我們也可以按照這種方式進行處理代理方法。

自定義代理實作

無傳回值的實作

功能描述:我們常常需要使用者的位置,是以實作擷取使用者的目前位置 1:首先建立一個CLLocationManager+Rx.swift檔案,然後導入CoreLocation、RxSwift、 RxCocoa等架構,檔案如下:

RxCocoa - DelegateProxy&amp;DelegateProxyTypeDelegateProxyDelegateProxyType為什麼需要DelegateProxy&amp;DelegateProxyType?Tableview的點選事件實作自定義代理實作

2:所有的擴充都是在.rx命名空間之後,我們可以先找到RxSwift下 Reactive.swift檔案檢視一下,為什麼我們的類能夠使用.rx,打開檔案,源碼如下:

/**
 Use `Reactive` proxy as customization point for constrained protocol extensions.

 General pattern would be:

 // 1. Extend Reactive protocol with constrain on Base
 // Read as: Reactive Extension where Base is a SomeType
 extension Reactive where Base: SomeType {
 // 2. Put any specific reactive extension for SomeType here
 }

 With this approach we can have more specialized methods and properties using
 `Base` and not just specialized on common base type.

 */

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<Base>結構體,ReactiveCompatible協定和ReactiveCompatible的擴充,擴充中包含了rx屬性。特别是最後一行代碼,為NSObject類添加擴充遵守ReactiveCompatible協定,這就是為什麼每一個繼承于NSObject的類都能夠使用.rx命名空間。

3:RxSwift提供的Ractive proxy模式,最主要的部分就是RxCocoa目錄下的oc檔案_RxDelegateProxy.h和 _RxDelegateProxy.m以及swift檔案DelegateProxy.swift和DelegateProxyType.swift。這些檔案中包含了RxSwift與任意架構的橋梁實作,能夠使用delegate(data sources)作為主要的資源提供資料。DelegateProxy對象建立了假的代理對象,該代理對象将為觀察者序列接收所有的資料。使用CCLocationManager來對比了解,圖形如下:

RxCocoa - DelegateProxy&amp;DelegateProxyTypeDelegateProxyDelegateProxyType為什麼需要DelegateProxy&amp;DelegateProxyType?Tableview的點選事件實作自定義代理實作

組合DelegateProxy和Reactive,将使我們的CLLocationManager擴充看起來跟所有官方RxCocoa extensions一樣。因為CLLocationManager需要一個代理,是以我們需要重建一個必要的代理來擷取資料,從locationManager的委托代理到專門的觀察者序列。映射非常簡單,一對一的關系,單一的協定函數将對應單一的觀察者序列并且傳回值。下面開始編寫代碼,進入CLLocationManager+Rx.swift檔案,實作如下:

class RxCLLocationManagerDelegateProxy: DelegateProxy,
CLLocationManagerDelegate, DelegateProxyType {
    class func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject) {
        let locationManager: CLLocationManager = object as! CLLocationManager
        locationManager.delegate = delegate as? CLLocationManagerDelegate
    }
    
    class func currentDelegateFor(_ object: AnyObject) -> AnyObject? {
        let locationManager: CLLocationManager = object as! CLLocationManager
        return locationManager.delegate
    }
}
           

1:RxCLLocationManagerDelegateProxy類即将成為我們的代理,隻要我們的觀察者序列被建立并訂閱之後會關聯到CLLocationManager的執行個體。 2:為委托代理實作DelegateProxyType協定中的setter和getter方法,通過這兩個函數我們能夠設定和擷取代理,代理能夠被用于擷取資料來自CLLocationManager執行個體并且連接配接到觀察者序列,這裡實作了在RxCocoa中怎樣使用自定義類實作代理模式

注意:基本的 Proxy 都是這樣設定的,主要是要繼承 DelegateProxy 和實作 DelegateProxyType ,别忘了加上我們自己實作類對應的代理,這裡是 CLLocationManagerDelegate。

接下來擴充Reactive,為Reactive添加屬性和方法,擴充中所有暴露的方法和屬性都能夠用于CLLocationManager的執行個體,增加didUpdateLocations屬性觀察位置的改變

extension Reactive where Base: CLLocationManager {
    var delegate: DelegateProxy {
        return RxCLLocationManagerDelegateProxy.proxyForObject(base)
    }
    
    var didUpdateLocations: Observable<[CLLocation]> {
        return delegate.methodInvoked(#selector(CLLocationManagerDelegate.locationManager(_:didUpdateLocations:))).map { parameters in
            return parameters[1] as! [CLLocation]
        }
    }
}
           

1:擷取委托代理,并使用建立的委托代理來擷取建立觀察者序列(observables)監聽位置(location)的改變 2:監聽didUpdateLocations屬性,delegate将作為委托代理監聽didUpdateLocations所有的調用,擷取資料轉換為數組[CLLocation],methodInvoked方法:是oc的代碼,位于RxCocoa中,對代理進行觀察。methodInvoked傳回一個觀察者序列并發送next事件,無論什麼時候具體的代理方法被調用。元素(elements)被包含在方法參數中。我們能夠通路參數,擷取需要的資料,這裡是一個數組,使用parameters[1] 擷取數組内容并轉換為[CLLocation]

到這裡我們已經可以開始使用了,如:

//擷取目前的位置
locationManager.rx.didUpdateLocations
    .map { locations in
        return locations[0]
    }
    .filter { location in
        return location.horizontalAccuracy < kCLLocationAccuracyHundredMeters
    }.subscribe(onNext: { location in
        print(location)
    }).disposed(by: bag)
           

上面是不是很簡單,如果需要其它功能,同樣是聲明屬性,調用methodInvoked執行對應的代理方法,擷取對應的資料,訂閱屬性擷取需要的部分。

有傳回值的實作

前面例子實作了執行對應的代理方法,但是代理方法并沒有傳回值,如果是有傳回值的代理方法,我們應該如何處理呢?我們使用MKMapView作為一個例子來說明如何擴充 UIKit component。為了開始擴充MKMapView,我們将采取跟CLLocationManager相同的模式,建立一個代理RxMKMapViewDelegateProxy并且為MKMapView擴充Reactive。

1:自定義RxMKMapViewDelegateProxy類,繼承于DelegateProxy并且遵守MKMapViewDelegate和DelegateProxyType協定

class RxMKMapViewDelegateProxy: DelegateProxy, MKMapViewDelegate, DelegateProxyType {
    class func currentDelegateFor(_ object: AnyObject) -> AnyObject? {
        let mapView: MKMapView = (object as? MKMapView)!
        return mapView.delegate
    }
    
    class func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject) {
        let mapView: MKMapView = (object as? MKMapView)!
        mapView.delegate = delegate as? MKMapViewDelegate
    }
}
           

2:擴充Reactive,實作擷取代理對象,通過代理對象監聽區域的改變

extension Reactive where Base: MKMapView {
    
    public var delegate: DelegateProxy {
        return RxMKMapViewDelegateProxy.proxyForObject(base)
    }
    
    //通過該函數,現在我們可以設定轉發函數( forwarding delegate ),而且能夠被調用,也能夠提供傳回值
    public func setDelegate(_ delegate: MKMapViewDelegate) -> Disposable {
        return RxMKMapViewDelegateProxy.installForwardDelegate(delegate,
                                                               retainDelegate: false,
                                                               onProxyForObject: self.base)
    }
   
    //為代理實作更多便利的通知機制,監聽使用者的拖動事件和mapview的導航事件
    public var reginDidChangeAnimated: ControlEvent<Bool> {
        let source = delegate
            .methodInvoked(#selector(MKMapViewDelegate.mapView(_:regionDidChangeAnimated:)))
            .map { parameters in
            return (parameters[1] as? Bool) ?? false
        }
        return ControlEvent(events: source)
    }
}
           

3:設定代理,并實作代理方法,然後可以直接使用了

//1
mapView.rx.setDelegate(self)
    .disposed(by: bag)
//2
extension ViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) ->
        MKOverlayRenderer {
            //do something...
            return MKOverlayRenderer()
    }
}
//3
mapView.rx.reginDidChangeAnimated
    .map { _  in
        self.mapView.centerCoordinate
    }
    .subscribe(onNext: { coordinate2D in
        print(coordinate2D)
    }).disposed(by: bag)
           

推薦:

RxDelegate

DelegateProxy.swift