Skip to content

📲 CoreBluetooth API's for modern Swift

License

Notifications You must be signed in to change notification settings

exPHAT/SwiftBluetooth

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SwiftBluetooth

Easily interface with Bluetooth peripherals in new or existing projects through modern async Swift API's.

Features

  • Parity with existing CoreBluetooth APIs for easy, incremental migration of existing projects
  • Modern, async-await API for discovery, connection, read/write, and more
  • Alternate callback-based API for codebases not using Swift Concurrency
  • Subscribe to peripheral discoveries, value updates, and more through AsyncStream
  • Easy await-ing of CentralManager state
  • Staticly typed characteristics
  • Thread safe
  • Zero inherited dependencies
  • Tested with included SwiftBluetoothMock library

Examples

API Documentation.

Complete example

Async API's make the entire Bluetooth lifecycle much simpler, using method names you're already familiar with from CoreBluetooth.

import SwiftBluetooth

let central = CentralManager()
try await central.waitUntilReady()

// Find and connect to the first available peripheral
let peripheral = await central.scanForPeripherals(withServices: [myService]).first!
try await central.connect(peripheral, timeout: connectionTimeout)

// Discover services and characteristics
let service = try await peripheral.discoverServices([myService]).first!
let _ = try await peripheral.discoverCharacteristics([.someCharacteristic], for: service)

// Read data directly from your characteristic
let value = try await peripheral.readValue(for: .someCharacteristic)

central.cancelPeripheralConnection(peripheral)

Callbacks

Stock CoreBluetooth methods now also have an additional overload that takes a completionHandler for projects not using Swift Concurrency.

central.connect(peripheral) { result in
  if result == .failure(let error) {
    // Issue connecting
    return
  }

  // Connected!
}

Methods often now have 3 overloads. One marked async, one with a completionHandler, and the original CoreBluetooth verision. Meaning you can choose whichever is most convienient at the time.

Stream discovered peripherals

Some operations (like scanning) conform to AsyncStream, meaning you can use for-await-in loops to iterate over new items.

for await peripheral in await central.scanForPeripherals() {
  print("Discovered:", peripheral.name ?? "Unknown")
}

Defining characteristics

Characteristics can be staticly defined on the stock Characteristic type, which removes the burden of keeping track of CBCharacteristic instances around your app.

extension Characteristic {
  static let someCharacteristic = Self("00000000-0000-0000-0000-000000000000")
}

// Use those characteristics later on your peripheral
try await myPeripheral.readValue(for: .someCharacteristic)

Watching with callbacks

Peristent tasks return a CancellableTask that needs to be cancelled when you're done.

let task = central.scanForPeripherals { peripheral in
  print("Discovered:", peripheral.name ?? "Unknown")
}

// At some point later, cancel the task to stop scanning
task.cancel()

Note Calling central.stopScan() will also cancel any peripheral scanning tasks

Migrate existing projects

Existing projects that already use CoreBluetooth can immediately get started by typealiasing the stock types. Afterwards, you can adopt async API's at your own pace.

import CoreBluetooth
import SwiftBluetooth // Add this

// Override existing CoreBluetooth classes to use SwiftBluetooth
typealias CBCentralManager         = SwiftBluetooth.CentralManager
typealias CBCentralManagerDelegate = SwiftBluetooth.CentralManagerDelegate
typealias CBPeripheral             = SwiftBluetooth.Peripheral
typealias CBPeripheralDelegate     = SwiftBluetooth.PeripheralDelegate

// Your existing code should continue to work as normal.
// But now you have access to all the new API's!

Install

Xcode

Add https://github.com/exPHAT/SwiftBluetooth.git in the "Swift Package Manager" tab.

Swift Package Manager

Add SwiftBluetooth as a dependency in your Package.swift file:

let package = Package(
  ...
  dependencies: [
    // Add the package to your dependencies
    .package(url: "https://github.com/exPHAT/SwiftBluetooth.git", branch: "master"),
  ],
  ...
  targets: [
    // Add SwiftBluetooth as a dependency on any target you want to use it in
    .target(name: "MyTarget",
            dependencies: [.byName(name: "SwiftBluetooth")])
  ]
  ...
)