AsyncBLE 0.1.0
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, andconnectWhenAvailable(_:)for reconnecting to a known device at launch- Connection state machine observable through
Connection.states, adapter availability throughCentral.adapterStates - Link drop and user-initiated disconnect are different transitions, not the same callback
- Async
read,write(with and without response), andnotifications(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 forData,Stringand 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 queueactiveConnections/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.