【iOS】RxSwift官方Example3--地理位置監(jiān)聽

前言

其實兽赁,這一篇的題目屿良,我覺得應該是RxSwift對代理的封裝色解,最后還是沿用官方Example的命名吧。

效果說明

圖一



圖二


圖一是當App可以使用定位信息時钥勋,顯示當前的經(jīng)緯度炬转。

圖二是當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的經(jīng)緯度信息綁定在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參數(shù)列表中,label的類型是泛型中的Base類型(例子是UILabel)良风,location是泛型中的CLLocationCoordinate2D對象谊迄。因此,我們只要在block中對label和location對象做操作即可烟央。

2统诺、封裝CLLocationManagerDelegate

2.1、創(chuàng)建CLLocationManagerDelegate的代理Proxy

為了創(chuàng)建Proxy對象疑俭,我們需要繼承DelegateProxy粮呢,并且遵守DelegateProxyType協(xié)議,該協(xié)議必須實現(xiàn)以下兩個方法钞艇。

/// 返回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啄寡、創(chuàng)建PublishSubject對象

先來簡單回顧一下概念:

subject的概念

Subject可以看做是一種代理和橋梁。它既是訂閱者又是訂閱源哩照,這意味著它既可以訂閱其他Observable對象挺物,同時又可以對它的訂閱者們發(fā)送事件。

PublishSubject的概念

當你訂閱PublishSubject的時候飘弧,你只能接收到訂閱他之后發(fā)生的事件

因此為了能夠成為代理的代理识藤,我們需要監(jiān)聽代理的事件,并且能夠讓外部進行監(jiān)聽次伶,所以我們創(chuàng)建了以下兩個publishSubject對象

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

將代理事件通過subject傳遞出去痴昧,記得調(diào)用_forwardToDelagate?.method方法,因為我們這里只是做了一層監(jiān)聽中轉

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做擴展

將想被監(jiān)聽的屬性赶撰,通過剛才創(chuàng)建Proxy傳遞出來,于是我們只需要創(chuàng)建對應的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()
    }
}

這里值得一提的是調(diào)后delegate.methodInvoked豪娜,會返回Observable<[Any]>,其中數(shù)組裝的就是傳遞給selector的參數(shù)哟楷,所以后面的map的block中侵歇,a[1]代表的就是CLAuthorizationStatus枚舉類型。

此外吓蘑,還定義了一個轉類型的函數(shù)惕虑,轉失敗后坟冲,會發(fā)出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創(chuàng)建一個新的可觀察序列
    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創(chuàng)建的。

deferred

deferred會等到有訂閱者的時候再通過工廠方法創(chuàng)建Observable對象伟叛,每個訂閱者訂閱的對象都是內(nèi)容相同而完全獨立的序列私痹。

因此,每次訂閱authorized信息時统刮,都會發(fā)送獨立的序列紊遵,確保每次都會響應。

總結

該Example可以當作封裝Delegate的介紹侥蒙,因此可以總結一下封裝流程如下:

  • 創(chuàng)建代理Proxy暗膜,需要繼承繼承DelegateProxy,并且遵守DelegateProxyType協(xié)議
  • 定義subject對象鞭衩,即訂閱者(訂閱代理)又是訂閱源(被外部訂閱)

之后的什么擴展学搜,service層就看大家的需要而定制了,但是以上的兩步是必須的论衍。最后瑞佩,有不正確的地方,歡迎指正~

Demo地址

https://github.com/maple1994/RxSwfitTest

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坯台,一起剝皮案震驚了整個濱河市炬丸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜒蕾,老刑警劉巖御雕,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異滥搭,居然都是意外死亡,警方通過查閱死者的電腦和手機捣鲸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門瑟匆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人栽惶,你說我怎么就攤上這事愁溜。” “怎么了外厂?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵冕象,是天一觀的道長。 經(jīng)常有香客問我汁蝶,道長渐扮,這世上最難降的妖魔是什么论悴? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮墓律,結果婚禮上膀估,老公的妹妹穿的比我還像新娘。我一直安慰自己耻讽,他們只是感情好察纯,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著针肥,像睡著了一般饼记。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上慰枕,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天具则,我揣著相機與錄音,去河邊找鬼捺僻。 笑死乡洼,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的匕坯。 我是一名探鬼主播束昵,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼葛峻!你這毒婦竟也來了锹雏?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤术奖,失蹤者是張志新(化名)和其女友劉穎礁遵,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體采记,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡佣耐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了唧龄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兼砖。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖既棺,靈堂內(nèi)的尸體忽然破棺而出讽挟,到底是詐尸還是另有隱情,我是刑警寧澤丸冕,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布耽梅,位于F島的核電站,受9級特大地震影響胖烛,放射性物質(zhì)發(fā)生泄漏眼姐。R本人自食惡果不足惜诅迷,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望妥凳。 院中可真熱鬧竟贯,春花似錦、人聲如沸逝钥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽艘款。三九已至持际,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哗咆,已是汗流浹背蜘欲。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留晌柬,地道東北人姥份。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像年碘,于是被迫代替她去往敵國和親澈歉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理屿衅,服務發(fā)現(xiàn)埃难,斷路器,智...
    卡卡羅2017閱讀 134,601評論 18 139
  • 本文章內(nèi)部分圖片資源來自RayWenderlich.com 本文結合自己的理解來總結介紹一下RxSwift最基本的...
    FKSky閱讀 2,856評論 4 14
  • RxSwift_v1.0筆記——13 Intermediate RxCocoa 這章將學習一些高級的RxCocoa...
    大灰很閱讀 648評論 1 1
  • 最近在學習RxSwift相關的內(nèi)容涤久,在這里記錄一些基本的知識點涡尘,以便今后查閱。 Observable 在RxSwi...
    L_Zephyr閱讀 1,740評論 1 4
  • 從街頭到街尾 春都枯萎 綠全滿上窗帷 還有 還有 池畔的紅褪 躲不過淚水
    林桉閱讀 193評論 0 0