RxSwift封装蓝牙库

为什么要用Rx

传统的编程方式大多都是告诉程序需要做什么、怎么做、什么时候做,并通过KVO、Notification、Delegate监听变化,同时又需要告诉系统什么时候会发生变化。ReactiveX可以帮助我们让代码自动相应更新,程序可以对底层数据的变化做出响应。

RxSwift是ReactiveX的Swift版,RxCocoa使用RxSwift对Cocoa APIs响应式编程的封装。

比如让一个Button响应单击事件,非响应式编程是这样:

button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)

func buttonAction() {
    ...
}

用了RxCocoa后变成这样:

button.rx.tap.subscribe(onNext: {
    ...
})

代码变得更加简洁,不需要定义多余的函数,从而可以让开发者更专注于业务逻辑,不再维护中间状态。

Rx的一些重点

Observables和Observers

两个重要的概念:ObservableObserver

  • Observable是发生变化的对象
  • Observer是接收变化通知的对象

多个Observer可以监听同一个Observable,Observable发生变化时会通知所有订阅的Observer。

Observable也相当于事件管道,会向订阅者发送事件信息。事件分为三种:

  • .Next(value) 有新的数据。
  • .Completed 管道已经结束。
  • .Error 有异常发生导致事件管道结束。

例如一个网络请求,收到服务端返回的数据后会发送.Next,因为请求都是一次性的,所以Next发送完后会马上发送Completed。如果请求超时了则会发送Error。

DisposeBag

DisposeBag是RxSwift提供的用来管理内存的工具。

当带有DisposeBag属性的对象调用deinit()时,bag将被清空,且每一个Observer会自动取消订阅它所观察的对象。理念类似于autoreleasepool。

如果没有DisposeBag,则会产生retain cycle,或者被意外释放掉,导致程序Crash。

let _disposeBag = DisposeBag()

Observable.just("123").subscribe(onNext: { (item) in

}).addDisposableTo(_disposeBag)

Disposables.create

每个Observable都要求Disposable类型的返回值,通过Disposables.create便能创建Disposable的实例,当Observable被释放时会执行Disposable,相当于析构函数。

Observable.create

实际开发中用的最多的函数,创建一个Observable。

Observable.create { (observer) -> Disposable in
    ...
    if (error) {
        observer.on(.error(RxSwiftError.unknown))
    } else {
        observer.on(.next(value))
        observer.on(.completed)
    }
    return Disposables.create {

    }
}

Observable.just

just只会发送一条数据,它先发送一次.Next(value),然后发送.Completed。

Observable.empty

empty是空管道,它只会发送.Completed消息。

Observable.deferred

deferred会等到有Observer的时候再去创建Observable,相当于懒加载,而且每个订阅者订阅的对象都是内容相同但指针不同的Observable。

Subjects

Subjet是observable和Observer之间的桥梁,它既是Obserable又是Observer,既可以发出事件,也可以订阅事件。

PublishSubject只能收到订阅它之后的事件,订阅之前的事件无法收到。

Debug

打印所有订阅、事件、Disposable。

sequenceThatErrors
    .retry(3)
    .debug()
    .subscribe(onNext: { print($0) })
    .addDisposableTo(disposeBag)

实践(封装CoreBluetooth)

PTRxBluetooth主要分为三个类:

  • PTCBCentralManager

    封装系统原生类CBCentralManager

    fileprivate let didUpdateStateSubject = PublishSubject<PTBluetoothState>()
    
    extension PTCBCentralManager: CBCentralManagerDelegate {
    
        public func centralManagerDidUpdateState(_ central: CBCentralManager) {
            if let bleState = PTBluetoothState(rawValue: central.state.rawValue) {
                didUpdateStateSubject.onNext(bleState)
            }
        }
    }
    

    第一步:定义一个PublishSubject类型的属性

    第二步:实现协议CBCentralManagerDelegate,在centralManagerDidUpdateState回调函数中,调用onNext将蓝牙状态发送给订阅者。

    每个回调函数都有一个与之相对应的PublishSubject属性,在函数执行时调用onNext将数据发送给订阅者,上层只需要订阅Subject就能得到数据状态,不再需要代理或callback。

  • PTBluetoothClient

    RxBluetooth的主要入口,封装了所有蓝牙操作。上层在检查蓝牙状态、搜寻设备、连接设备时主要与这个类交互。

    //监听手机的蓝牙开启状态
    public var state: Observable<PTBluetoothState> {
        return .deferred {
            return self.base.centralManager.rx.didUpdateState.startWith(self.base.centralManager.state)
        }
    }
    

    第一步:调用了.deferred,deferred会等到有Observer订阅它的时候去创建,并且给每个Observer都创建一个新对象。

    第二步:调用centralMangaer的函数didUpdateState,并使用startWith赋初始值,通常是unknown。

    关于startWith可以参考官方文档startWith

    //搜寻周围的蓝牙设备
    public func scanForPeripherals() -> Observable<PTCBPeripheral> {
        
        base.scanPeripheral.removeAll()
        return .deferred {
            
            let observable = Observable.create { (element: AnyObserver<PTCBPeripheral>) -> Disposable in
                
                let disposeable = self.base.centralManager.rx.didDiscoverPeripheral
                .map { return $0 }
                .subscribe(onNext: { (peripheral) in
                    
                    if self.base.scanPeripheral.contains(peripheral) == false {
                        self.base.scanPeripheral.append(peripheral)
                        element.onNext(peripheral)
                    }
                })
                
                self.base.centralManager.scanForPeripherals(withServices: nil, options: nil)
                
                return Disposables.create {
                    disposeable.dispose()
                    self.base.centralManager.stopScan()
                }
            }
            .subscribeOn(self.base.subscriptionQueue)
            .publish()
            .refCount()
            
            return self.base.ensureBluetoothState(.poweredOn, observable: observable)
        }
    }
    

    第一步:创建一个Observable。

    第二步:订阅centralManager的didDiscoverPeripheral,当搜寻到新设备时,发送给observable的订阅者。

    第三步:调用函数scanForPeripherals开始搜寻蓝牙设备。

    第四步:observable释放时,取消订阅didDiscoverPeripheral,并停止搜寻设备。

    第五步:调用publish和refcount,这样做是为了将冷信号(Cold Observable)转成热信号(Hot Observable)。

    1.Hot Observable是主动的,既是没有订阅者,它依然会发送事件。Cold Observable是被动的,只有出现订阅者后,它才会发送事件。

    2.Hot Observable可以有多个订阅者,Cold Observable只能一对一,当出现多个订阅者时,事件会从新发送。

    具体参考:
    refcount
    publish

    //尝试连接蓝牙设备
    public func connect(_ peripheral: PTCBPeripheral, options: [String: Any]? = nil) -> Observable<PTCBPeripheral> {
        
        let success = base.centralManager.rx.didConnectPeripheral
            .filter { $0 == peripheral }
            .take(1)
            .map { _ in return peripheral }
        
        let error = base.centralManager.rx.didFailConnectPeripheral
            .filter { $0.0 == peripheral }
            .take(1)
            .flatMap { (peripheral, error) -> Observable<PTCBPeripheral> in return .empty() }
        
        let observable = Observable<PTCBPeripheral>.create { (observer) -> Disposable in
            
            guard peripheral.isConnected == false else {
                observer.onNext(peripheral)
                observer.onCompleted()
                return Disposables.create()
            }
            
            let disposable = success.amb(error).subscribe(observer)
            
            self.base.centralManager.connect(peripheral, options: options)
            
            return Disposables.create {
                if peripheral.isConnected == false {
                    self.base.centralManager.cancelPeripheralConnection(peripheral)
                    disposable.dispose()
                }
            }
        }
        
        return observable
    }
    

    第一步:订阅连接成功和连接失败的Observable。

    第二步:创建一个Observable。

    第三步:调用amb将success和error绑定在一起,amb的主要作用就是将多个Observable绑定,但是只有第一个Observable能够发送onNext,其他的Observable只能发送onError和onCompleted。因为连接失败是没有onNext的,所以这里用到了amb。

  • PTCBPeripheral

    PTCBPeripheral封装系统类CBPeripheral,主要用来与蓝牙设备交互,比如接收蓝牙数据、发送数据给设备。

    extension PTCBPeripheral: CBPeripheralDelegate {
    
        public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
            let services = peripheral.services ?? []
            services.forEach {
                peripheralDidDiscoverServicesSubject.onNext($0)
            }
        }
    
        public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
            let characteristics = service.characteristics ?? []
            peripheralDidDiscoverCharacteristicsSubject.onNext(characteristics)
        }
    
        public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
            peripheralDidUpdateValueSubject.onNext(characteristic)
        }
    }
    

    实现CBPeripheralDelegate代理,当搜寻到Services和Characteristics时,相对应的PublishSubject会发送数据给订阅者。

    上层需要搜寻设备的Service时,只需要这样:

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

推荐阅读更多精彩内容