You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Make ReliaBLEManager a nonisolatedSendable final class that forwards async calls to BluetoothActor. Collapse the now-internal BluetoothManager into the actor type (renaming BluetoothManager → BluetoothActor). Delete dead code.
Context
See the investigation report at docs/investigations/swift6-concurrency-audit-2026-05-13.md, sections Investigator Findings §4, Recommendations §A ("Why nonisolated ReliaBLEManager and not@MainActor"), and Risks §7 (naming).
This step finishes the architectural goal: the library is callable cleanly from @MainActor SwiftUI and from background actors, without forcing a MainActor hop on background callers. A @MainActor façade would have forced that hop; a nonisolated Sendable façade does not.
Deliverable (Step 4 of 5 in the migration plan)
Mark public final class ReliaBLEManager: Sendable. All members become forwarders.
Public actions become async (authorizeBluetooth, startScanning, stopScanning, future connect/disconnect).
currentState becomes get async.
Rename BluetoothManager → BluetoothActor. Move the file's responsibilities entirely inside the actor; remove the legacy NSObject shape. Keep BluetoothManager.swift as the filename (it now declares the actor) so DocC anchor history stays sane, or rename to BluetoothActor.swift if anchor breakage is acceptable — call this out in the PR for review.
Collapse / inline PeripheralManager into the actor (its queue.sync pattern becomes plain actor isolation). The standalone PeripheralManager type goes away.
Delete dead code:
testFunction() in ReliaBLEManager.swift:106
AuthorizationError.unauthorized if it's still unused after the refactor (the only case currently is denied/restricted/unknown)
PreserveforceMock: true literal at the factory call site (load-bearing per CLAUDE.md).
Update the Demo's CentralViewModel to call await manager.… everywhere; wrap actions in Task { … } from @MainActor view methods.
Acceptance criteria
ReliaBLEManager: Sendable. A unit test captures a ReliaBLEManager reference into a Task.detached and calls every public method — must compile.
BluetoothActor is the global actor. BluetoothManager symbol no longer exists.
PeripheralManager symbol no longer exists (inlined into the actor).
All public methods that mutate / read state are async.
forceMock: true literal remains at the factory call site.
DocC catalog updated (architecture page reflects the renames; ReliaBLEManager → BluetoothActor link from the manager page).
Not in scope
Logging polish + strict-concurrency flag flip is Step 5.
Removing the Combine surface happened in Step 3 (Remove Combine #12); this step can assume AsyncStream is already in place.
Goal
Make
ReliaBLEManagera nonisolatedSendable final classthat forwardsasynccalls toBluetoothActor. Collapse the now-internalBluetoothManagerinto the actor type (renamingBluetoothManager→BluetoothActor). Delete dead code.Context
See the investigation report at
docs/investigations/swift6-concurrency-audit-2026-05-13.md, sections Investigator Findings §4, Recommendations §A ("Why nonisolatedReliaBLEManagerand not@MainActor"), and Risks §7 (naming).This step finishes the architectural goal: the library is callable cleanly from
@MainActorSwiftUI and from background actors, without forcing a MainActor hop on background callers. A@MainActorfaçade would have forced that hop; a nonisolatedSendablefaçade does not.Deliverable (Step 4 of 5 in the migration plan)
public final class ReliaBLEManager: Sendable. All members become forwarders.async(authorizeBluetooth,startScanning,stopScanning, futureconnect/disconnect).currentStatebecomesget async.BluetoothManager→BluetoothActor. Move the file's responsibilities entirely inside the actor; remove the legacyNSObjectshape. KeepBluetoothManager.swiftas the filename (it now declares the actor) so DocC anchor history stays sane, or rename toBluetoothActor.swiftif anchor breakage is acceptable — call this out in the PR for review.PeripheralManagerinto the actor (itsqueue.syncpattern becomes plain actor isolation). The standalonePeripheralManagertype goes away.testFunction()inReliaBLEManager.swift:106AuthorizationError.unauthorizedif it's still unused after the refactor (the only case currently isdenied/restricted/unknown)forceMock: trueliteral at the factory call site (load-bearing perCLAUDE.md).CentralViewModelto callawait manager.…everywhere; wrap actions inTask { … }from@MainActorview methods.Acceptance criteria
ReliaBLEManager: Sendable. A unit test captures aReliaBLEManagerreference into aTask.detachedand calls every public method — must compile.BluetoothActoris the global actor.BluetoothManagersymbol no longer exists.PeripheralManagersymbol no longer exists (inlined into the actor).async.forceMock: trueliteral remains at the factory call site.ReliaBLEManager→BluetoothActorlink from the manager page).Not in scope
AsyncStreamis already in place.References
docs/investigations/swift6-concurrency-audit-2026-05-13.mdBluetoothManagerSwift 6 safe #13 (Step 1), Step 2 issue, Remove Combine #12 (Step 3)