在RxCocoa里面封装了大量系统UI组件的扩展,我们可以仿照RxCocoa里面的封装方式,给自己的代理也添加Rx的扩展。
大概原理就是通过DelegateProxy把delegate的方法调用转换为一个observable sequence。
这里我们用CLLocationManager代理的封装作为例子。
1.创建一个RXCLLocationManagerDelegateProxy类
open class RXCLLocationManagerDelegateProxy:
DelegateProxy<CLLocationManager, CLLocationManagerDelegate>,
DelegateProxyType,
CLLocationManagerDelegate {
public weak private(set) var locationManager: CLLocationManager?
public init(locationManager: ParentObject) {
self.locationManager = locationManager
super.init(parentObject: locationManager, delegateProxy: RXCLLocationManagerDelegateProxy.self)
}
public static func registerKnownImplementations() {
self.register { RXCLLocationManagerDelegateProxy(locationManager: $0) }
}
public class func currentDelegate(for object: ParentObject) -> CLLocationManagerDelegate? {
return object.delegate
}
public class func setCurrentDelegate(_ delegate: CLLocationManagerDelegate?, to object: ParentObject) {
object.delegate = delegate
}
}
2.让CLLocationManager遵循HasDelegate
extension CLLocationManager: HasDelegate {
public typealias Delegate = CLLocationManagerDelegate
}
3.给CLLocationManager创建Reactive扩展
extension Reactive where Base: CLLocationManager {
public var delegate: RXCLLocationManagerDelegateProxy {
return RXCLLocationManagerDelegateProxy.proxy(for: base)
}
public var didUpdateLocations: ControlEvent<(manager: CLLocationManager, locations: [CLLocation])> {
let sel = #selector(CLLocationManagerDelegate.locationManager(_:didUpdateLocations:))
let source = delegate.methodInvoked(sel).map{ (args) -> (manager: CLLocationManager, locations: [CLLocation]) in
let manager = try castOrThrow(CLLocationManager.self, args[0])
let locations = try castOrThrow(Array<CLLocation>.self, args[1])
return (manager, locations)
}
return ControlEvent(events: source)
}
public var didError: ControlEvent<(manager: CLLocationManager, error: Error)> {
let didFailWithErrorSel = #selector(CLLocationManagerDelegate.locationManager(_:didFailWithError:))
let didFinishDeferredUpdatesWithErrorSel = #selector(CLLocationManagerDelegate.locationManager(_:didFinishDeferredUpdatesWithError:))
let generalError = delegate
.methodInvoked(didFailWithErrorSel)
.map(clErrorEvent)
let updatesError = delegate
.methodInvoked(didFinishDeferredUpdatesWithErrorSel)
.map(clErrorEvent)
let source = Observable.of(generalError, updatesError).merge()
return ControlEvent(events: source)
}
private func clErrorEvent(_ args: [Any]) throws -> (manager: CLLocationManager, error: Error) {
let manager = try castOrThrow(CLLocationManager.self, args[0])
let error = try castOrThrow(Error.self, args[1])
return (manager, error)
}
public var didErrorRangingBeacons: ControlEvent<(manager: CLLocationManager, region: CLBeaconRegion, error: Error)> {
let rangingBeaconsDidFailForRegionSel = #selector(CLLocationManagerDelegate.locationManager(_:rangingBeaconsDidFailFor:withError:))
let source = delegate
.methodInvoked(rangingBeaconsDidFailForRegionSel)
.map { (args) -> (manager: CLLocationManager, region: CLBeaconRegion, error: Error) in
let manager = try castOrThrow(CLLocationManager.self, args[0])
let beaconRegion = try castOrThrow(CLBeaconRegion.self, args[1])
let error = try castOrThrow(Error.self, args[2])
return (manager, beaconRegion, error)
}
return ControlEvent(events: source)
}
}
注意
RxCocoa的 castOrThrow 这个方法没有加public修饰,所以我们是没法调用,我们需要Copy出来.
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
}
然后我们就可以使用了
llocationManager.rx
.didUpdateLocations
.subscribe(onNext: { (_, locations) in
print(locations)
})
.disposed(by: disposeBag)
locationManager.rx
.didError
.subscribe(onNext: { (_, error) in
print(error)
})
.disposed(by: disposeBag)
locationManager.rx
.didErrorRangingBeacons
.subscribe(onNext: { (_, beacons, _) in
print(beacons)
})
.disposed(by: disposeBag)