CoreBluetoothForUnity をカスタマイズして使いやすくする

## 本記事について

この記事は [CoreBluetoothForUnity Advent Calendar 2023](https://qiita.com/advent-calendar/2023/core-bluetooth-for-unity) の8日目の記事です。

CoreBluetoothForUnity は、Unity で iOS の CoreBluetooth を使うためのライブラリです。その目的の都合上 API は元となる CoreBluetooth とほぼ同じになっています。

では、CoreBluetooth の API は Unity で開発する上で使いやすいのか？というと、全くそうではないと考えます。
具体的には delegate がコード追いづらいため async/await で書けるようにするなどが挙げられます。

本記事では iOS ではどのように開発されているか、Unity で簡単にカスタマイズするにはどうするのかについて紹介します。

## 環境

- CoreBluetoothForUnity 0.4.3

## iOS ではどのように扱われているか

async/await, Rx で CoreBluetooth を扱うライブラリがありました。

https://github.com/manolofdez/AsyncBluetooth

https://github.com/StarryInternet/CombineCoreBluetooth

https://github.com/Polidea/RxBluetoothKit

これら既存のライブラリの API を参考にカスタマイズするのは良さそうに思えます。

参考

https://note.com/shu223/n/nece33e7080ab

## UniRx と　UniTask を使ってカスタマイズする

ここでは別々に同じタイミングで呼ばれた時どうする？といったことは考えず、さくっとカスタマイズする方法を紹介します。

ソース全文は以下にアップロードしました。

[Gist](https://gist.github.com/teach310/eb61fdd764e483fac95470504220066d#file-observablecentralmanagerdelegate-cs)


### UniRx で CoreBluetooth のイベントを Observable にする

```ObservableCentralManagerDelegate.cs
using System;
using CoreBluetooth;
using UniRx;

public class ObservableCentralManagerDelegate : ICBCentralManagerDelegate, IDisposable
{
    // NOTE: Connectの結果が成功か失敗かを判定するために、errorを追加
    readonly Subject<(CBCentralManager central, CBPeripheral peripheral, CBError error)> _didConnectPeripheralSubject = new Subject<(CBCentralManager central, CBPeripheral peripheral, CBError error)>();
    ...
    public IObservable<(CBCentralManager central, CBPeripheral peripheral, CBError error)> DidConnectPeripheralAsObservable() => _didConnectPeripheralSubject;
    ...
    void ICBCentralManagerDelegate.DidConnectPeripheral(CBCentralManager central, CBPeripheral peripheral)
    {
        _didConnectPeripheralSubject.OnNext((central, peripheral, null));
    }
...
    public void Dispose()
    {
        _didConnectPeripheralSubject.Dispose();
        ...
    }
}
```

このようにタプルでまとめて、Subject の OnNext に渡すのが最も手っ取り早いと思います。
DidConnectPeripheral と　DidFailToConnectPeripheral は結果を await で待ちやすくするためにまとめています。

ドキュメントより、どちらかしか呼ばれない認識です。

[connect](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1518766-connect)

これによって以下のように書けるようになります。

```.cs
_observableCentralManagerDelegate.DidConnectPeripheralAsObservable().Subscribe(x =>
            {
                // 処理
            }).AddTo(this);
```

また UniTask で以下のように待つこともできるようになります。

```.cs
var result = await _observableCentralManagerDelegate.DidConnectPeripheralAsObservable().ToUniTask(useFirstValue: true, cancellationToken: cancellationToken);
```

### UniRx, UniTask を利用して　02_ButtonInformation の Central を書き換える

前

https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.3/Assets/CoreBluetooth/Samples/02_ButtonInformation/SampleButtonInformation_Central.cs

後

[CustomCentral.cs](https://gist.github.com/teach310/eb61fdd764e483fac95470504220066d#file-customcentral-cs)

細かい点が異なりますが、概ね同じ処理になるように書き換えました。

```CustomCentral.cs
        async UniTask ConnectAsync(CancellationToken cancellationToken)
        {
            if (_centralManager.State != CBManagerState.PoweredOn)
            {
                await UniTask.WaitUntil(() => _centralManager.State == CBManagerState.PoweredOn, cancellationToken: cancellationToken);
            }
            _stateLabel.text = "Scanning...";
            _peripheral = await ScanAsync(cancellationToken);
            _peripheral.Delegate = _observablePeripheralDelegate;
            _centralManager.StopScan();
            _stateLabel.text = "Connecting...";
            await ConnectPeripheralAsync(cancellationToken);
            _stateLabel.text = "Connected";
            await DiscoverServicesAsync(cancellationToken);
            await DiscoverCharacteristicsAsync(cancellationToken);
            SetNotifyValue();
        }
```

async, await を使うことで処理を手続き的に書くことができています。
接続で何をやるのかが一目でわかるため私はこちらのほうが好みです。

行数は増えてしまっています。CBCentralManager をラップして Async なメソッドを追加することでより簡潔になりそうです。

## おわりに

本記事では UniRx, UniTask を使って CoreBluetoothForUnity をカスタマイズする方法を紹介しました。

紹介したやり方はあくまでも例です。
状況にあわせてより良いやり方はたくさんあると思いますのでぜひチャレンジしてみてください。
その時に本記事の内容が参考になれば幸いです！