这个库是对 CoreBluetooth 和 CoreBluetoothMock 的 Swift 6 封装,它允许你使用 Swift 并发来编写你的核心蓝牙代码。它也能很好地与 SwiftUI 配合使用。
主要类是:
CentralManager
,它封装了 CBCentralManager
和 CBMCentralManager
。它与 CBCentralManager
非常相似,但它的所有方法都是 async
的,并且它提供了一个状态属性 @Observable
属性。Peripheral
,它封装了 CBPeripheral
和 CBMPeripheral
。它与 CBPeripheral
非常相似,但它的所有方法都是 async
的,并且它提供了一个状态属性,带有 @Observable
属性。如果您了解 CoreBluetooth API,您应该能够毫无问题地使用此库。
这个库只支持中心角色,不支持外围角色。这意味着您只能使用这个库来扫描和连接到外围设备,而不能使用它来广播或充当外围设备。该功能可能会在未来添加,但由于将苹果设备用作外围设备不是很常见,因此优先级不高。
它也处于开发的早期阶段,所以以下是一些尚未实现的功能:
centralManager(_: CBMCentralManager, willRestoreState dict: [String: Any])
centralManager(_: CBMCentralManager, connectionEventDidOccur event: CBMConnectionEvent, for cbPeripheral: CBMPeripheral)
centralManager(_: CBMCentralManager, didUpdateANCSAuthorizationFor cbPeripheral: CBMPeripheral)
centralManager(_: CBMCentralManager, didDisconnectPeripheral peripheral: CBMPeripheral, timestamp: CFAbsoluteTime, isReconnecting: Bool, error: Error?)
peripheral(_: CBMPeripheral, didModifyServices invalidatedServices: [CBMService])
peripheral(_: CBMPeripheral, didDiscoverIncludedServicesFor service: CBMService, error: Error?)
peripheral(_: CBMPeripheral, didUpdateNotificationStateFor characteristic: CBMCharacteristic, error: Error?)
peripheral(_: CBMPeripheral, didDiscoverDescriptorsFor characteristic: CBMCharacteristic, error: Error?)
peripheral(_: CBMPeripheral, didUpdateValueFor descriptor: CBMDescriptor, error: Error?)
peripheral(_: CBMPeripheral, didWriteValueFor descriptor: CBMDescriptor, error: Error?)
所以最基本的功能可以正常工作,但其余的功能还在开发中。
将 AsyncCoreBluetooth 作为依赖项添加到您的 Package.swift
文件中。
dependencies: [
.package(url: "https://github.com/meech-ward/AsyncCoreBluetooth.git", from: "0.1.0")
]
然后将其添加到您的目标依赖项中。
targets: [
.target(
name: "YourTarget",
dependencies: ["AsyncCoreBluetooth"]),
]
查看示例 iOS 应用程序以获取完整示例:https://github.com/meech-ward/AsyncCoreBluetoothExample
查看 example-no-ui.md 以获取连接示例。
以下是一些代码片段:
通过调用 start()
或 startStream()
来设置 central manager 并检查当前的 ble 状态。它们做的事情相同,但 startStream()
返回一个 AsyncStream
,您可以使用它来监听 ble 状态的变化。
import AsyncCoreBluetooth
let centralManager = CentralManager()
// centralManager.start() if you don't need to listen for changes to the ble state
for await bleState in await centralManager.startStream() {
switch bleState {
case .unknown:
print("Unkown")
case .resetting:
print("Resetting")
case .unsupported:
print("Unsupported")
case .unauthorized:
print("Unauthorized")
case .poweredOff:
print("Powered Off")
case .poweredOn:
print("Powered On, ready to scan")
}
}
CentralManager
还通过其状态属性提供 @Observable
属性,用于 ble 状态,以便于与 SwiftUI 一起使用。
import AsyncCoreBluetooth
struct ContentView: View {
var centralManager = CentralManager()
var body: some View {
NavigationStack {
VStack {
switch centralManager.state.bleState {
case .unknown:
Text("Unkown")
case .resetting:
Text("Resetting")
case .unsupported:
Text("Unsupported")
case .unauthorized:
Text("Unauthorized")
case .poweredOff:
Text("Powered Off")
case .poweredOn:
Text("Powered On, ready to scan")
}
}
.padding()
.navigationTitle("App")
}
.task {
await centralManager.start()
// or startStream if you want the async stream returned from start
}
}
}
您的应用程序应该处理 ble 状态的所有可能情况。有人关闭蓝牙或打开飞行模式非常常见,您的应用程序的 UI 应该反映这些状态。但是,最常见的情况是 .poweredOn
,因此如果您只想在设备处于该状态时立即运行代码,则可以使用以下代码:
_ = await centralManager.startStream().first(where: {$0 == .poweredOn})
请记住,熟悉 Swift 并发将使使用此库变得更容易。
import AsyncCoreBluetooth
let heartRateServiceUUID = UUID(string: "180D")
do {
let peripherals = try await centralManager.scanForPeripherals(withServices: [heartRateServiceUUID])
let peripheral = peripherals[heartRateServiceUUID]
print("found peripheral \(peripheral)")
} catch {
// This only happens when ble state is not powered on or you're already scanning
print("error scanning for peripherals \(error)")
}
SwiftUI
import AsyncCoreBluetooth
struct ScanningPeripherals: View {
let heartRateServiceUUID = UUID(string: "180D")
var centralManager: CentralManager
@MainActor @State private var peripherals: Set<Peripheral> = []
var body: some View {
VStack {
List(Array(peripherals), id: \.identifier) { peripheral in
Section {
ScannedPeripheralRow(centralManager: centralManager, peripheral: peripheral)
}
}
}
.task {
do {
for await peripheral in try await centralManager.scanForPeripherals(withServices: [heartRateServiceUUID]) {
peripherals.insert(peripheral)
// break out of the loop or terminate the continuation to stop the scan
}
} catch {
// This only happens when ble state is not powered on or you're already scanning
print("error scanning for peripherals \(error)")
}
}
}
}
与 CBCentralManager
一样,CentralManager
具有 connect
和 cancelPeripheralConnection
方法。 但是,这两种方法都返回一个可丢弃的 AsyncStream
,您可以使用它来监视外围设备的连接状态。
@discardableResult public func connect(_ peripheral: Peripheral, options: [String: Any]? = nil) async throws -> AsyncStream<Peripheral.ConnectionState> {
@discardableResult public func cancelPeripheralConnection(_ peripheral: Peripheral) async throws -> AsyncStream<Peripheral.ConnectionState> {
enum ConnectionState {
case disconnected(CBError?)
case connecting
case connected
case disconnecting
case failedToConnect(CBError)
}
disconnected(nil)
connect()
将导致 connectionState 更改为 connectingfailedToConnect()
disconnect()
将导致 connectionState 更改为 disconnecting
disconnecting
后,设备将更改为 disconnected(nil)
disconnected(error)
还有以下方法,您可以随时为外围设备获取一个 AsyncStream<Peripheral.ConnectionState>
func connectionState(forPeripheral peripheral: Peripheral) async -> AsyncStream<Peripheral.ConnectionState>
最重要的是,peripheral.state.connectionState
是 @Observable
,用于连接状态,以便于与 SwiftUI 一起使用。
let centralManager = CentralManager()
await centralManager.startStream().first(where: { $0 == .poweredOn })
print("Powered On, ready to scan")
let peripheral = try await centralManager.scanForPeripherals(withServices: nil).first()
连接
for await connectionState in await centralManager.connect(peripheral) {
print(connectionState)
}
// or
await centralManager.connect(peripheral)
for await connectionState in centralManager.connectionState(forPeripheral: peripheral) {
print(connectionState)
}
断开连接
for await connectionState in await centralManager.cancelPeripheralConnection(peripheral) {
print(connectionState)
}
// or
await centralManager.cancelPeripheralConnection(peripheral)
for await connectionState in centralManager.connectionState(forPeripheral: peripheral) {
print(connectionState)
}
您可以随意请求新的异步流或跳出这些流,而不会干扰外围设备的连接。 一旦您调用 connect,连接将由核心蓝牙正常管理。 您可以随时调用 cancelPeripheralConnection
以取消连接。
connect
cancelPeripheralConnection
swift test --no-parallel