Learn RxSwift The Hard Way - Geolocation (三)

这里我们来从头实现 RxExample 的第三个例子:GeolocationExample。如果不知道如何开始,可以参考之前的几篇博文。

你可以先运行一下代码,看一下效果。每当我们改变对应用程序定位的授权,界面都会发生相应的改变。

GeolocationService

首先我们来看提供定位功能的类:

class GeolocationService {

    private (set) var autorized: Driver<Bool>
    private (set) var location: Driver<CLLocationCoordinate2D>
    
    static let instance = GeolocationService()
    private let locationManager = CLLocationManager()
    
    private init() {...}
    
}

这里我省略了 init 的内容,先来看接口,之后我们会回过头来看具体的实现。很显然,这里的 GeolocationService 是一个标准的单例。而前两个对于外部只读的变量,就是这个 Service 对外提供的接口。

不难猜测,这里的 autorized 表示是否有定位权限。目前知道这些就够了,先了解骨架,后面再深入细节。

GeolocationViewController

我们来看 GeolocationViewController 的核心代码。

// TAG: 终极版本
geolocationService
    .autorized
    .drive(noGeolocationView.rx_driveAuthorization)
    .addDisposableTo(disposeBag)

噫…这里的 drive 是什么,rx_driveAuthorization 又是什么?我们先来看 rx_driveAuthorization

private extension UIView {
    var rx_driveAuthorization: AnyObserver<Bool> {
        return UIBindingObserver(UIElement: self) { view, authorized in
            if authorized {
                view.hidden = true
                view.superview?.sendSubviewToBack(view)
            }
            else {
                view.hidden = false
                view.superview?.bringSubviewToFront(view)
            }
        }.asObserver()
    }
}

Tip:这里将 extension 声明为 private,可以将其限制在该代码文件中,类似的效果可以参考 Swift:Selector 语法糖

rx_driveAuthorization 为一个计算属性,类型为 AnyObserver<Bool>。虽然目前不知道 UIBindingObserver 是干什么用的,不过从代码可以推测出来,通过一个 authorized 布尔类型的变量来控制视图的状态。

来看 UIBindingObserver 的构造函数

init(UIElement: UIElementType, binding: (UIElementType, Value) -> Void)

UIElement 为 UI 元素,后面为一个函数,从 binding 这个函数名也可以看出,当订阅的事件发生的时候,会调用这个函数。其中第二个参数 Value 就是这个 observer 的订阅消息类型。

理解到这里,总结一下。rx_driveAuthorization 为一个订阅者(observer),订阅的消息类型是 bool,然后根据这个值来作出视图的相应变化。

我们先不管上面的 drive,如果按照我们之前的做法,如何来实现这个功能呢?虽然不推荐,但是我们可以写出下面容易理解的代码:

// TAG: 版本1
geolocationService.autorized
    .asObservable()
    .subscribeNext { [weak self] (autorized) in
        self?.noGeolocationView.rx_driveAuthorization.onNext(autorized)
    }
    .addDisposableTo(disposeBag)

Tip: 这里如果不用 weak self,会造成循环引用哦。

上面的方式应该是最容易理解的。首先将 autorized 转为 Observable,然后订阅 next 事件,然后显式地发送 onNext 事件。虽然这种方式可行,但是有些不妥的。我们来一步一步优化。

改写成如下代码:

// TAG: 版本2
geolocationService.autorized
    .asObservable()
    .subscribe(noGeolocationView.rx_driveAuthorization)

这里直接用 subscribe 的方式来订阅。其实如果进入源码看的话,和上面我们实现的方式差不多,不过除了 next 事件,还有 complete 等事件的处理。

好了,现在版本 2 和我们的终极版本已经很像了。我来看看,drive 到底做了些什么。

Tip: 其实我们不使用 drive 也可以完成相应的功能,就像上面那样。这些操作符 Unit(不知道怎么翻译),其实属于 RxCocoa,并不是标准的 Rx 框架。但是通过使用这些 Unit,确实可以让我们编程更加方便。详情可以参考最后的参考链接。

drive 源码:

public func drive<O: ObserverType where O.E == E>(observer: O) -> Disposable {
    MainScheduler.ensureExecutingOnScheduler()
    return self.asObservable().subscribe(observer)
}

从这里我们可以看出,其实 drive 函数保证了之后的操作是在主线程的。下面列举了使用 Unit 的一些好处:

  • 不会发送错误 (错误会导致 dispose)
  • 工作在主线程 (对于 UI 操作,不用再切换线程)
  • 共享同一个值 (不用再使用 shareReplay)

OK,现在我们已经完全过渡到终极版本了。Nice work!

我们现在回过头来看 GeolocationServiceinit 方法。

片段:

autorized = Observable.deferred { [weak locationManager] in
        let status = CLLocationManager.authorizationStatus() // 1
        guard let locationManager = locationManager else {
            return Observable.just(status)
        }
        return locationManager
            .rx_didChangeAuthorizationStatus // 2
            .startWith(status) // 3
    }
    .asDriver(onErrorJustReturn: CLAuthorizationStatus.NotDetermined) // 4
    .map { // 5
        switch $0 {
        case .AuthorizedAlways:
            return true
        default:
            return false
        }
    }
  1. 首先获取了一次地理位置授权状态
  2. 这里使用了 RxCocoa 的扩展,监听授权状态的变化(相比于 delegate 的方式,是不是爽多了)
  3. 将之前的 status 插入到序列的开头
  4. 将 Observable 转为 Observable,因为它使不回发送 error 的,所以这里要告诉它如果发生 error 直接发送 .CLAuthorizationStatus.NotDetermined
  5. 最后将其 map 到 bool 类型

OK,location 也是差不多的逻辑。你可以自己琢磨看看。

如果对 Observable.deferred 不是很理解,可以看看 这里

示例代码可以参见 RxSwiftExample,或者这里

参考资料

https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Units.md
http://t.swift.gg/d/39-021-units

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

推荐阅读更多精彩内容