見出し画像

最近のCore BluetoothのOSS

2022年1月25日現在。

AsyncBluetooth

11スター、最終更新は 14 hours ago。

oobaさんが使っていたのを機に知った。

こんな感じで、async/awaitを使って書ける。

  • スキャン

let centralManager = CentralManager()

try await centralManager.waitUntilReady()

var scanDataStream = try await centralManager.scanForPeripherals(withServices: nil)
for await scanData in scanDataStream {
    // Check scan data...
}

await centralManager.stopScan()
  • 接続

try await centralManager.connect(peripheral, options: nil)
  • Read

let value: String? = try await peripheral.readValue(
    forCharacteristicWithUUID: UUID(uuidString: "")!,
    ofServiceWithUUID: UUID(uuidString: "")!
)

RequirementsとしてiOS 15+

サンプル(別リポジトリにある)はSwiftUIで書かれている。

Xcode 13.2以降を使えばiOS 13+で使用できる?

AsyncBluetoothは前述の通りiOS 15+となっているが、Xcode 13.2(厳密にはXcode 13.2.1)以降でビルドすればiOS 13以上で利用可能になるのか?

結論から言うと、iOS 13では不可、iOS 14以上では利用可能

ビルドしてみたところ、内部でLoggerというiOS 14/macOS 11で追加されたAPIを利用していて、そこで引っかかった。

public struct AsyncBluetooth {
    static let commonLogger = Logger(
        subsystem: Bundle(for: CentralManager.self).bundleIdentifier ?? "",
        category: "common"
    )
}

Package.swiftでiOS 14.0, macOS 11.0を指定してビルドしなおせば、iOS 14以上では利用可能になる。

let package = Package(
    name: "AsyncBluetooth",
    platforms: [
        .macOS("11.0"),
        .iOS("14.0")
    ],

(ちなみにサンプル内ではiOS 15のAPIを利用しているため、サンプルはそのままではiOS 14では利用できない。)

CombineCoreBluetooth

39スター。最終更新は2021年9月。

その名の通り、Combine + Core Bluetooth。

CombineCoreBluetooth is a library that bridges Apple's `CoreBluetooth` framework and Apple's `Combine` framework, making it possible to subscribe to perform bluetooth operations while subscribing to a publisher of the results of those operations, instead of relying on implementing delegates and manually filtering for the results you need.
(CombineCoreBluetooth は Apple の CoreBluetooth フレームワークと Apple の Combine フレームワークを橋渡しするライブラリで、デリゲートの実装や必要な結果の手動フィルタリングに頼らず、Bluetooth 操作を実行しながらその結果のパブリッシャーをサブスクライブすることが可能です。)

こんな感じで書ける。

// use whatever ids your peripheral advertises here
let serviceID = CBUUID(string: "0123")
let characteristicID = CBUUID(string: "4567")

peripheral
  .readValue(forCharacteristic: characteristicID, inService: serviceID)
  .sink(receiveCompletion: { completion in
    // handle any potential errors here
  }, receiveValue: { data in
   // handle data from characteristic here, or add more publisher methods to map and transform it.
  })
  .store(in: &cancellables)

依存関係の設計にはpointfreeのアプローチを採用しているらしい。

This library is heavily inspired by pointfree.co's approach to designing dependencies, but with some customizations. Many asynchronous operations returns their own `Publisher` or expose their own long-lived publisher you can subscribe to. To do something like fetching a value from a characteristic, for instance, you could call the following methods on the `Peripheral` type and subscribe to the resulting `Publisher`:
(このライブラリは、pointfree.coの依存関係設計のアプローチに大きく影響を受けていますが、いくつかのカスタマイズが施されています。多くの非同期処理はそれ自身のパブリッシャーを返すか、購読可能な長寿命の パブリッシャーを公開します。例えば、ある特性から値を取得するような処理を行う場合、Peripheral 型に対して以下のメソッドを呼び出し、その結果得られる Publisher にサブスクライブすることができます。)

RequirementsとしてはiOS 13+。

SwiftyBluetooth

145スター、最終更新は14days ago.

Closures based APIs for CoreBluetooth.

ということで、デリゲートじゃなくてクロージャで書けるようにするライブラリ。

SwiftyBluetooth.scanForPeripherals(withServiceUUIDs: nil, timeoutAfter: 15) { scanResult in
    switch scanResult {
        case .scanStarted:
            // The scan started meaning CBCentralManager scanForPeripherals(...) was called 
        case .scanResult(let peripheral, let advertisementData, let RSSI):
            // A peripheral was found, your closure may be called multiple time with a .ScanResult enum case.
            // You can save that peripheral for future use, or call some of its functions directly in this closure.
        case .scanStopped(let error):
            // The scan stopped, an error is passed if the scan stopped unexpectedly
    }
}

iOS 10+。

SwiftyTeeth

19スター、最終更新は2021年9月。

コンセプトとしてはSwiftyBluetoothと同様にクロージャベースで書けるようにすることと、簡単に扱えることを目指したものらしい。

SwiftyTeeth is a simple, lightweight library intended to take away some of the cruft and tediousness of using iOS BLE. It replaces CoreBluetooth's protocols and delegates with a callback-based pattern, and handles much of the code overhead associated with handling connections, discovery, reads/writes, and notifications. It is a spiritually similar library to Android's Blueteeth.
(SwiftyTeethは、iOSのBLEを使用する際の煩雑さと退屈さを取り除くことを目的とした、シンプルで軽量なライブラリです。CoreBluetooth のプロトコルやデリゲートをコールバックベースのパターンに置き換え、接続、検出、読み取り/書き込み、通知の処理に関連するコードのオーバーヘッドを大幅に処理します。これは、Android の Blueteeth と精神的に類似したライブラリです。)

## High-Level
The motivation for this library was to provide an alternate (ideally - simpler) API than what iOS offers in CoreBluetooth to reduce complexity and code overhead. Another motivator was to provide an API that might make more sense 'practically' than CoreBluetooth's API, which exposes underlying implementation-specifics rather than abstracting them away.
(このライブラリの動機は、iOS が CoreBluetooth で提供しているものとは別の (理想的にはよりシンプルな) API を提供し、複雑さとコードのオーバーヘッドを軽減することでした。もう一つの動機は、CoreBluetooth の API よりも「実用的」な API を提供することです。この API は、実装固有の問題を抽象化するのではなく、その根底を露呈させるものです。)

RequirementsはiOS 10+。

今ならどれを使うか?

個人的には、Core Bluetooth関連のOSSは十分に枯れていないことが多い印象で、プロダクトとして作り込む場合はサードパーティ製ライブラリは使わず素のCore Bluetoothを使うようにしている。

デモやプロトタイプであれば、AsyncBluetoothを利用してみると思う。(前述の通り、iOS 15+という厳しい制約はXcode 13.2を利用すれば解消するので)

ここから先は

0字
文章やサンプルコードは多少荒削りかもしれませんが、ブログや書籍にはまだ書いていないことを日々大量に載せています。たったの400円で、すぐに購読解除してもその月は過去記事もさかのぼって読めるので、少しでも気になる内容がある方にはオトクかと思います。

技術的なメモやサンプルコード、思いついたアイデア、考えたこと、お金の話等々、頭をよぎった諸々を気軽に垂れ流しています。

最後まで読んでいただきありがとうございます!もし参考になる部分があれば、スキを押していただけると励みになります。 Twitterもフォローしていただけたら嬉しいです。 https://twitter.com/shu223/