RxSwift封裝藍牙庫

為什么要用Rx

傳統(tǒng)的編程方式大多都是告訴程序需要做什么漩符、怎么做思恐、什么時候做荒吏,并通過KVO敛惊、Notification、Delegate監(jiān)聽變化绰更,同時又需要告訴系統(tǒng)什么時候會發(fā)生變化豆混。ReactiveX可以幫助我們讓代碼自動相應更新,程序可以對底層數(shù)據(jù)的變化做出響應动知。

RxSwift是ReactiveX的Swift版,RxCocoa使用RxSwift對Cocoa APIs響應式編程的封裝员辩。

比如讓一個Button響應單擊事件盒粮,非響應式編程是這樣:

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

func buttonAction() {
    ...
}

用了RxCocoa后變成這樣:

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

代碼變得更加簡潔,不需要定義多余的函數(shù)奠滑,從而可以讓開發(fā)者更專注于業(yè)務(wù)邏輯丹皱,不再維護中間狀態(tài)。

Rx的一些重點

Observables和Observers

兩個重要的概念:ObservableObserver

  • Observable是發(fā)生變化的對象
  • Observer是接收變化通知的對象

多個Observer可以監(jiān)聽同一個Observable宋税,Observable發(fā)生變化時會通知所有訂閱的Observer摊崭。

Observable也相當于事件管道,會向訂閱者發(fā)送事件信息杰赛。事件分為三種:

  • .Next(value) 有新的數(shù)據(jù)呢簸。
  • .Completed 管道已經(jīng)結(jié)束。
  • .Error 有異常發(fā)生導致事件管道結(jié)束乏屯。

例如一個網(wǎng)絡(luò)請求根时,收到服務(wù)端返回的數(shù)據(jù)后會發(fā)送.Next,因為請求都是一次性的辰晕,所以Next發(fā)送完后會馬上發(fā)送Completed蛤迎。如果請求超時了則會發(fā)送Error。

DisposeBag

DisposeBag是RxSwift提供的用來管理內(nèi)存的工具含友。

當帶有DisposeBag屬性的對象調(diào)用deinit()時替裆,bag將被清空校辩,且每一個Observer會自動取消訂閱它所觀察的對象。理念類似于autoreleasepool辆童。

如果沒有DisposeBag宜咒,則會產(chǎn)生retain cycle,或者被意外釋放掉胸遇,導致程序Crash荧呐。

let _disposeBag = DisposeBag()

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

}).addDisposableTo(_disposeBag)

Disposables.create

每個Observable都要求Disposable類型的返回值,通過Disposables.create便能創(chuàng)建Disposable的實例纸镊,當Observable被釋放時會執(zhí)行Disposable倍阐,相當于析構(gòu)函數(shù)。

Observable.create

實際開發(fā)中用的最多的函數(shù)逗威,創(chuàng)建一個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只會發(fā)送一條數(shù)據(jù),它先發(fā)送一次.Next(value)凯旭,然后發(fā)送.Completed概耻。

Observable.empty

empty是空管道,它只會發(fā)送.Completed消息罐呼。

Observable.deferred

deferred會等到有Observer的時候再去創(chuàng)建Observable鞠柄,相當于懶加載,而且每個訂閱者訂閱的對象都是內(nèi)容相同但指針不同的Observable嫉柴。

Subjects

Subjet是observable和Observer之間的橋梁厌杜,它既是Obserable又是Observer,既可以發(fā)出事件计螺,也可以訂閱事件夯尽。

PublishSubject只能收到訂閱它之后的事件,訂閱之前的事件無法收到登馒。

Debug

打印所有訂閱匙握、事件、Disposable陈轿。

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

實踐(封裝CoreBluetooth)

PTRxBluetooth主要分為三個類:

  • PTCBCentralManager

    封裝系統(tǒng)原生類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類型的屬性

    第二步:實現(xiàn)協(xié)議CBCentralManagerDelegate圈纺,在centralManagerDidUpdateState回調(diào)函數(shù)中,調(diào)用onNext將藍牙狀態(tài)發(fā)送給訂閱者济欢。

    每個回調(diào)函數(shù)都有一個與之相對應的PublishSubject屬性赠堵,在函數(shù)執(zhí)行時調(diào)用onNext將數(shù)據(jù)發(fā)送給訂閱者,上層只需要訂閱Subject就能得到數(shù)據(jù)狀態(tài)法褥,不再需要代理或callback茫叭。

  • PTBluetoothClient

    RxBluetooth的主要入口,封裝了所有藍牙操作半等。上層在檢查藍牙狀態(tài)揍愁、搜尋設(shè)備呐萨、連接設(shè)備時主要與這個類交互。

    //監(jiān)聽手機的藍牙開啟狀態(tài)
    public var state: Observable<PTBluetoothState> {
        return .deferred {
            return self.base.centralManager.rx.didUpdateState.startWith(self.base.centralManager.state)
        }
    }
    

    第一步:調(diào)用了.deferred莽囤,deferred會等到有Observer訂閱它的時候去創(chuàng)建谬擦,并且給每個Observer都創(chuàng)建一個新對象。

    第二步:調(diào)用centralMangaer的函數(shù)didUpdateState朽缎,并使用startWith賦初始值惨远,通常是unknown。

    關(guān)于startWith可以參考官方文檔startWith

    //搜尋周圍的藍牙設(shè)備
    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)
        }
    }
    

    第一步:創(chuàng)建一個Observable话肖。

    第二步:訂閱centralManager的didDiscoverPeripheral北秽,當搜尋到新設(shè)備時,發(fā)送給observable的訂閱者最筒。

    第三步:調(diào)用函數(shù)scanForPeripherals開始搜尋藍牙設(shè)備贺氓。

    第四步:observable釋放時,取消訂閱didDiscoverPeripheral床蜘,并停止搜尋設(shè)備辙培。

    第五步:調(diào)用publish和refcount,這樣做是為了將冷信號(Cold Observable)轉(zhuǎn)成熱信號(Hot Observable)邢锯。

    1.Hot Observable是主動的扬蕊,既是沒有訂閱者,它依然會發(fā)送事件丹擎。Cold Observable是被動的厨相,只有出現(xiàn)訂閱者后,它才會發(fā)送事件鸥鹉。

    2.Hot Observable可以有多個訂閱者,Cold Observable只能一對一庶骄,當出現(xiàn)多個訂閱者時毁渗,事件會從新發(fā)送。

    具體參考:
    refcount
    publish

    //嘗試連接藍牙設(shè)備
    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单刁。

    第二步:創(chuàng)建一個Observable灸异。

    第三步:調(diào)用amb將success和error綁定在一起,amb的主要作用就是將多個Observable綁定羔飞,但是只有第一個Observable能夠發(fā)送onNext肺樟,其他的Observable只能發(fā)送onError和onCompleted。因為連接失敗是沒有onNext的逻淌,所以這里用到了amb么伯。

  • PTCBPeripheral

    PTCBPeripheral封裝系統(tǒng)類CBPeripheral,主要用來與藍牙設(shè)備交互卡儒,比如接收藍牙數(shù)據(jù)田柔、發(fā)送數(shù)據(jù)給設(shè)備俐巴。

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

    實現(xiàn)CBPeripheralDelegate代理,當搜尋到Services和Characteristics時硬爆,相對應的PublishSubject會發(fā)送數(shù)據(jù)給訂閱者欣舵。

    上層需要搜尋設(shè)備的Service時,只需要這樣:

    aPeripheral.rx.discoverServices(nil).subscribe(onNext: { (service) in
    
    }).addDisposableTo(_disposeBag)
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缀磕,一起剝皮案震驚了整個濱河市缘圈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌袜蚕,老刑警劉巖糟把,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異廷没,居然都是意外死亡糊饱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門颠黎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來另锋,“玉大人,你說我怎么就攤上這事狭归∝财海” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵过椎,是天一觀的道長室梅。 經(jīng)常有香客問我,道長疚宇,這世上最難降的妖魔是什么亡鼠? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮敷待,結(jié)果婚禮上间涵,老公的妹妹穿的比我還像新娘。我一直安慰自己榜揖,他們只是感情好勾哩,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著举哟,像睡著了一般思劳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妨猩,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天潜叛,我揣著相機與錄音,去河邊找鬼壶硅。 笑死钠导,一個胖子當著我的面吹牛震嫉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播牡属,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼票堵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了逮栅?” 一聲冷哼從身側(cè)響起悴势,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎措伐,沒想到半個月后特纤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡侥加,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年捧存,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片担败。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡昔穴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出提前,到底是詐尸還是另有隱情吗货,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布狈网,位于F島的核電站宙搬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拓哺。R本人自食惡果不足惜勇垛,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望士鸥。 院中可真熱鬧窥摄,春花似錦、人聲如沸础淤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸽凶。三九已至,卻和暖如春建峭,著一層夾襖步出監(jiān)牢的瞬間玻侥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工亿蒸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凑兰,地道東北人掌桩。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像姑食,于是被迫代替她去往敵國和親波岛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

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