【iOS】RxSwift官方Example3--地理位置监听

前言

其实,这一篇的题目,我觉得应该是RxSwift对代理的封装,最后还是沿用官方Example的命名吧。

效果说明

图一



图二


图一是当App可以使用定位信息时,显示当前的经纬度。

图二是当App被禁止使用定位信息时,显示的提示信息

代码解释

比起上两个Example,这个Example复杂的多了。主要复杂在对Delegate的封装。

如何使用RxSwift对Delegate的封装稍后再说,先看看封装后的使用。

let service = GeolocationService.instance
        // 将是否允许使用定位的“Bool”绑定noGeolocationView.rx.isHidden
        service.authorized
        .drive(noGeolocationView.rx.isHidden)
        .addDisposableTo(disposeBag)
        // 将定位信息绑定在showLocationLabel.rx.coordinates
        service.location
        .drive(showLocationLabel.rx.coordinates)
        .addDisposableTo(disposeBag)

1、对UILabel的扩展

可以看到上面的例子,将CLLocationCoordinate2D的经纬度信息绑定在label上了。当想绑定的在视图信息越多,我们就需要对UILabel进行扩展。

扩展方法如下:

/*
意思就是当Reactive的Base对象是UILabel时,增加一个类型为UIBindingObserver<Base, CLLocationCoordinate2D>的coordinates属性。
*/ 
private extension Reactive where Base: UILabel {
    var coordinates: UIBindingObserver<Base, CLLocationCoordinate2D> {
        return UIBindingObserver(UIElement: base, binding: { (label, location) in
            label.text = "Lat: \(location.latitude)\nLon: \(location.longitude)"
        })
    }
}

在后面的block参数列表中,label的类型是泛型中的Base类型(例子是UILabel),location是泛型中的CLLocationCoordinate2D对象。因此,我们只要在block中对label和location对象做操作即可。

2、封装CLLocationManagerDelegate

2.1、创建CLLocationManagerDelegate的代理Proxy

为了创建Proxy对象,我们需要继承DelegateProxy,并且遵守DelegateProxyType协议,该协议必须实现以下两个方法。

/// 返回object的代理对象
    class func currentDelegateFor(_ object: AnyObject) -> AnyObject? {
        let locationManager: CLLocationManager = object as! CLLocationManager
        return locationManager.delegate
    }

    /// 设置objct代理对象
    class func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject) {
        let locationManager: CLLocationManager = object as! CLLocationManager
        if let delegate = delegate {
            locationManager.delegate = (delegate as! CLLocationManagerDelegate)
        } else {
            locationManager.delegate = nil
        }
    }

2.2、创建PublishSubject对象

先来简单回顾一下概念:

subject的概念

Subject可以看做是一种代理和桥梁。它既是订阅者又是订阅源,这意味着它既可以订阅其他Observable对象,同时又可以对它的订阅者们发送事件。

PublishSubject的概念

当你订阅PublishSubject的时候,你只能接收到订阅他之后发生的事件

因此为了能够成为代理的代理,我们需要监听代理的事件,并且能够让外部进行监听,所以我们创建了以下两个publishSubject对象

internal lazy var didUpdateLocationsSubject = PublishSubject<[CLLocation]>()
    internal lazy var didFailWithErrorSubject = PublishSubject<Error>()

将代理事件通过subject传递出去,记得调用_forwardToDelagate?.method方法,因为我们这里只是做了一层监听中转

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)
    }

代理和proxy之间的层级关系图:

      +-------------------------------------------+
      |                                           |                           
      | 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)    |                           
      |                                           |
      +-------------------------------------------+   

2.3、对CLLocationManager做扩展

将想被监听的属性,通过刚才创建Proxy传递出来,于是我们只需要创建对应的Observable。

代码如下:

extension Reactive where Base: CLLocationManager {
    /**
     Reactive wrapper for `delegate`.
     
     For more information take a look at `DelegateProxyType` protocol documentation.
     */
    public var delegate: DelegateProxy {
        return RxCLLocationManagerDelegateProxy.proxyForObject(base)
    }
    
    // MARK: Responding to Authorization Changes
    
    /**
     Reactive wrapper for `delegate` message.
     */
    public var didChangeAuthorizationStatus: Observable<CLAuthorizationStatus> {
        return delegate.methodInvoked(#selector(CLLocationManagerDelegate.locationManager(_:didChangeAuthorization:)))
            .map { a in
                let number = try castOrThrow(NSNumber.self, a[1])
                return CLAuthorizationStatus( rawValue: Int32(number.intValue)) ?? .notDetermined
        }
    }
    
    // MARK: Responding to Location Events
    
    /**
     Reactive wrapper for `delegate` message.
     */
    public var didUpdateLocations: Observable<[CLLocation]> {
        return (delegate as! RxCLLocationManagerDelegateProxy).didUpdateLocationsSubject.asObservable()
    }
    
    /**
     Reactive wrapper for `delegate` message.
     */
    public var didFailWithError: Observable<Error> {
        return (delegate as! RxCLLocationManagerDelegateProxy).didFailWithErrorSubject.asObservable()
    }
}

这里值得一提的是调后delegate.methodInvoked,会返回Observable<[Any]>,其中数组装的就是传递给selector的参数,所以后面的map的block中,a[1]代表的就是CLAuthorizationStatus枚举类型。

此外,还定义了一个转类型的函数,转失败后,会发出Error

fileprivate 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
}

2.4、定义service层

class GeolocationService {
static let instance = GeolocationService()
private (set) var authorized: Driver<Bool>
private (set) var location: Driver<CLLocationCoordinate2D>

private let locationManager = CLLocationManager()

private init() {
    
    locationManager.distanceFilter = kCLDistanceFilterNone
    locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
    
    // deferred会为每一位订阅者observer创建一个新的可观察序列
    authorized = Observable.deferred { [weak locationManager] in
        let status = CLLocationManager.authorizationStatus()
        guard let locationManager = locationManager else {
            return Observable.just(status)
        }
        return locationManager
            .rx.didChangeAuthorizationStatus
            .startWith(status)
        }
        .asDriver(onErrorJustReturn: CLAuthorizationStatus.notDetermined)
        .map {
            switch $0 {
            case .authorizedAlways:
                return true
            default:
                return false
            }
    }
    
    location = locationManager.rx.didUpdateLocations
        .asDriver(onErrorJustReturn: [])
        .flatMap {
            return $0.last.map(Driver.just) ?? Driver.empty()
        }
        .map { $0.coordinate }
    locationManager.requestAlwaysAuthorization()
    locationManager.startUpdatingLocation()
}
}

这里的service层就是将之前扩展的LocationManager再次封装。值得一提的是,这里的authorized是使用deferred创建的。

deferred

deferred会等到有订阅者的时候再通过工厂方法创建Observable对象,每个订阅者订阅的对象都是内容相同而完全独立的序列。

因此,每次订阅authorized信息时,都会发送独立的序列,确保每次都会响应。

总结

该Example可以当作封装Delegate的介绍,因此可以总结一下封装流程如下:

  • 创建代理Proxy,需要继承继承DelegateProxy,并且遵守DelegateProxyType协议
  • 定义subject对象,即订阅者(订阅代理)又是订阅源(被外部订阅)

之后的什么扩展,service层就看大家的需要而定制了,但是以上的两步是必须的。最后,有不正确的地方,欢迎指正~

Demo地址

https://github.com/maple1994/RxSwfitTest

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 本文章内部分图片资源来自RayWenderlich.com 本文结合自己的理解来总结介绍一下RxSwift最基本的...
    FKSky阅读 2,877评论 4 14
  • RxSwift_v1.0笔记——13 Intermediate RxCocoa 这章将学习一些高级的RxCocoa...
    大灰很阅读 658评论 1 1
  • 最近在学习RxSwift相关的内容,在这里记录一些基本的知识点,以便今后查阅。 Observable 在RxSwi...
    L_Zephyr阅读 1,752评论 1 4
  • 从街头到街尾 春都枯萎 绿全满上窗帷 还有 还有 池畔的红褪 躲不过泪水
    林桉阅读 194评论 0 0