Skip to content

AsyncBLE 0.1.0

Choose a tag to compare

@vilimets vilimets released this 05 Sep 10:24
· 3 commits to main since this release

A Swift concurrency wrapper over CoreBluetooth, central role. No delegates, no CoreBluetooth types in public signatures, and reconnection you configure instead of write.

Why it exists

Three things are painful in every CoreBluetooth codebase, and every codebase solves them again from scratch.

Connect timeouts. CoreBluetooth has none — connect(_:options:) waits forever and tells you nothing. connect(_:timeout:) imposes a deadline and withdraws the pending request when it expires.

Connection state. The truth is normally spread across CBPeripheral.state, a handful of delegate callbacks, and whatever booleans got added under deadline. Here it is one AsyncStream of a four-case enum, driven by a pure state machine that is unit-tested without hardware.

Reconnection. The OS already retries a pending connect indefinitely; what it never does is tell you what is happening, or give up. ReconnectPolicy decides when to stop waiting, and the wait itself is observable as reconnecting(attempt:).

What's in it

  • connect(_:timeout:) with a configurable deadline, and connectWhenAvailable(_:) for reconnecting to a known device at launch
  • Connection state machine observable through Connection.states, adapter availability through Central.adapterStates
  • Link drop and user-initiated disconnect are different transitions, not the same callback
  • Async read, write (with and without response), and notifications(for:) — all I/O on a connection ordered through one FIFO queue, because actor reentrancy alone reorders concurrent callers
  • Notification subscriptions restored automatically across a reconnect; in-flight I/O fails fast while reconnecting rather than hanging
  • Lazy service and characteristic discovery, cached per link and flushed on reconnect
  • Typed characteristics: Characteristic<Value> pairs a UUID with the type of its value, with built-in codecs for Data, String and the fixed-width integers
  • OSLog-backed logging with a pluggable LogHandler, six categories, four levels
  • withRaw { peripheral, central in ... } escape hatch, scoped to the library's queue
  • activeConnections / disconnectAll() for auditing links an app forgot to close

No CoreBluetooth type appears in any public signature. The one exception is CBUUID, spelled CharacteristicID and ServiceID so that consuming code needs no CoreBluetooth import at all — the bundled example app has none.

Requirements

iOS 16+, Swift 5.9+, Swift Package Manager.

.package(url: "https://github.com/vilimets/AsyncBLE-Swift.git", from: "0.1.0")

Notes

239 tests, covering the state machine's full transition table, the delegate bridge, the discovery cache, the I/O queue and the reconnect path against hand-written fakes — no hardware required. Verified against real hardware by hand.

Peripheral role, background modes and state restoration, L2CAP, RSSI and descriptors are deliberately out of 0.1.0 and tracked as issues.