RxBluetoothKitでBLEデバイスの複数サービス／キャラクタリスティックにアクセスする

複数のBLEサービス／キャラクタリスティックにアクセスする必要があり、RxBluetoothKitを使って実現しました。

#やりたいこと
以下のことを実行する必要がありました。
(1) ServiceA の CharacteristicA1 をread
(2) ServiceB の CharacteristicB1 をread
(3) ServiceB の CharacteristicB2 をsubscribeし、(2)の回数分だけ飛んでくるnotifyを受け取る

#環境
Xcode: 10.1
Swift: 4.2.1
RxBluetoothKit: 5.1.4

#RxBluetoothKit
https://github.com/Polidea/RxBluetoothKit

#ソースコード
##1. Serviceの定義
```
enum BLEService: String, ServiceIdentifier {
    case serviceA = "ServiceAのUUID"
    case serviceB = "ServiceBのUUID"
    
    var uuid: CBUUID {
        return CBUUID(string: self.rawValue)
    }
}
```

##2. Chracteristicの定義
```
enum BLECharacteristic: String, CharacteristicIdentifier {
    case charA1 = "CharacteristicA1のUUID"
    case charB1 = "CharacteristicB1のUUID"
    case charB2 = "CharacteristicB2のUUID"
    
    var uuid: CBUUID {
        return CBUUID(string: self.rawValue)
    }
    
    var service: ServiceIdentifier {
        switch self {
        case .charA1:
            return BLEService.serviceA
        case .charB1, .charB2:
            return BLEService.serviceB
        }
    }
}
```

##3. BLEデバイスのスキャン・接続
電源OFF・接続できないときにエラーとして返すため、タイムアウト3秒を設定しました。
またpoweredOn状態になったイベント、接続先デバイスを各1つに限定するために、take(1)を入れています。
※iOS11, 12では、Control CenterからBluetoothが完全オフにはできなくなり、「未接続」状態へ切り替えるようになっています。
その状態では CBCentralManagerOptionShowPowerAlertKey を設定していてもアラートは出ません。
poweredOn状態にもならないため、そのタイムアウトとして捕捉できます。

```
func connect() -> Observable<Peripheral> {
    let options = [CBCentralManagerOptionShowPowerAlertKey: true] as [String : AnyObject]
    let manager = CentralManager(queue: .main, options: options)
    
    return manager.observeState()
        .startWith(self.manager.state)
        .filter { $0 == .poweredOn }
        .timeout(3.0, scheduler: MainScheduler.instance)
        .take(1)
        .flatMap { _ in self.manager.scanForPeripherals(withServices: [BLEService.serviceA.uuid, BLEService.serviceB.uuid]) }
        .timeout(3.0, scheduler: MainScheduler.instance)
        .take(1)
        .flatMap { $0.peripheral.establishConnection() }
}
```

##4. 複数サービスのread
次にこれらを行います。
(1) ServiceA の CharacteristicA1 をread
(2) ServiceB の CharacteristicB1 をread
Observable.concat で複数の処理をまとめることができます。
※受け取りデータの変換処理は省略

```
func readData() {
    connect()
        .flatMap {
            Observable.concat(
                $0.readValue(for: BLECharacteristic.charA1).asObservable(),
                $0.readValue(for: BLECharacteristic.charB1).asObservable()
            )}
        .subscribe(onNext: { (char) in
            switch char.uuid {
            case BLECharacteristic.charA1.uuid:
            	let data = char.value
            case BLECharacteristic.charB1.uuid:
            	let data = char.value
            	
            	// notify受け取りの呼び出し
            	getNotify(char: char, notifyCount: Int(data))
            	
        }, onError: { (error) in
            // エラー処理
        }, onCompleted: {
        }, onDisposed: {
        })
}
```

##5. readの結果に基づいたnotify受け取り
これを行います。
(3) ServiceB の CharacteristicB2 をsubscribeし、(2)の回数分だけ飛んでくるnotifyを受け取る
このメソッドの呼び出しは、先ほどの「4. 複数サービスの read」の onNext で行なっています。

```
func getNotify(char : Characteristic, notifyCount : Int) {
    char.service.discoverCharacteristics([BLECharacteristic.charB2.uuid]).asObservable()
        .flatMap { Observable.from($0) }
        .flatMap { $0.observeValueUpdateAndSetNotification() }
        .take(notifyCount)
        .subscribe(onNext: { (char) in
            let data = char.value
        }, onError: { (error) in
            // エラー処理
        }, onCompleted: {
        }, onDisposed: {
        })
}

```

##6. メモリリーク防止のため、Observableのdispose
take(n)を行えば、n回ぶん値を受け取ったあと、自動的にObservableがdisposeされます。
「5. readの結果に基づいたnotify受け取り」ではそれを実装していますが、「4. 複数サービスのread」では実装していません。
「4. 複数サービスのread」で実装すると、「5. readの結果に基づいたnotify受け取り」を行う前にBluetooth接続がdisposeされてしまい、エラーになってしまうためです。

ただし、そうするとObservableが解放されず、メモリリークが発生してしまいます。

そこで readData で生成したObservableをメンバ変数として保持し、次回アクセス時にdisposeするように対処しました。
これであれば、何度BLEアクセスを行なってもメモリリークは発生しなくなります。
（あまりきれいな対処ではありませんが…。もし良いアイデアがありましたらご意見いただきたいです）

```
func readData() {

    if let d = disposable {
        d.dispose()
    }
    
    disposable =
    connect()
        .flatMap ... // 以下は「4. 複数サービスのread」参照
}
```

#まとめ
個人的にRxを使ったのは初めてだったのですが、RxBluetoothKitを使うことにより、Core Bluetoothをそのまま使うよりもシンプルに書けるかと思います。