Skip to content

Releases: vilimets/AsyncBLE-Swift

AsyncBLE 0.1.1

Choose a tag to compare

@vilimets vilimets released this 05 Sep 11:09

Housekeeping. No API changes and no behaviour changes — a drop-in update from 0.1.0.

Swift 6 strict concurrency

The library now builds warning-free. Five diagnostics that the compiler flags as errors under the Swift 6 language mode are gone:

  • Two "reference to captured var self". A weak capture is a var, because it can become nil, and the queue hop nested inside a stream's termination handler was reading one from concurrently-executing code. The reference is now promoted to a strong local before the hop — which also means that once termination has established the object is alive, the cleanup is guaranteed to run instead of quietly becoming a no-op partway through.
  • Two non-Sendable closure captures. The resumption callback behind Connection's suspension helpers, and the work handed to the connection's scheduler, both cross into @Sendable closures. Both are now declared @Sendable, which states what was already true rather than changing it.
  • DispatchWorkItem in a Sendable type. It is not Sendable-audited, but cancelling one from another thread is precisely what it is for, and cancelling is all the wrapper does with it. The assumption is now stated with @preconcurrency at the Dispatch import, matching how the CoreBluetooth imports handle the same problem.

None of this touches the isolation design. Mutable state still lives in queue-confined classes reached through one serial queue that is both CoreBluetooth's queue and the actors' executor, and the deployment floor is still iOS 16.

Documentation

Removed the last references to the development plan, which is not part of the repository. Comments that cited it are rewritten rather than stripped, so the reasoning they carried is still there.

Requirements

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

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

AsyncBLE 0.1.0

Choose a tag to compare

@vilimets vilimets released this 05 Sep 10:24

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.