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
Refactor the Demo app's SwiftData layer to run on a background actor rather than @MainActor, so that the Demo itself exercises ReliaBLE across actor boundaries. Catches @MainActor-creep regressions in the library at PR-time and doubles as a concrete pattern for library consumers to copy.
Context
See the investigation report at docs/investigations/swift6-concurrency-audit-2026-05-13.md, Preventive Measures section. Quoting the maintainer's note:
I was trying to keep the Demo app simple, but I think this is a case where it would make sense to implement background SwiftData in the Demo. Both to catch regressions and demonstrate the ability to library consumers.
Today CentralViewModel runs on @MainActor and performs SwiftData inserts/upserts directly in Combine .sink closures. After the library refactor (#10 and its sub-issues), the manager will be a nonisolated Sendable type with AsyncStream event surfaces. The Demo can exercise this by:
Holding the ReliaBLEManager reference on a @MainActor view-model (typical SwiftUI path).
Routing SwiftData writes through a background actor (@ModelActor or a custom isolated ModelContext wrapper).
Driving the BLE events from .task { for await … in manager.peripheralDiscoveries } and hopping into the SwiftData actor for persistence.
This is also a useful demonstration for library consumers building real-world apps where BLE event volume warrants off-main persistence.
Deliverable
Introduce a @ModelActor (or equivalent) wrapper around the SwiftData ModelContainer in the Demo. Suggested name: DeviceStoreActor.
Route all writes (peripheral upserts, discovery-event inserts) through this actor.
Keep reads on @MainActor for SwiftUI binding (or move to @Observable actor-snapshot pattern if desired).
The Demo's discovery pipeline becomes:
.task {forawaiteventin manager.peripheralDiscoveries {await store.upsert(event) // background actor
}}
Validate that the existing UX (CentralView, PeripheralView) keeps working — no main-thread checker complaints, no UI stutters.
Acceptance criteria
Demo builds clean under Swift 6 strict concurrency.
All SwiftData writes are observable on a non-main thread (verify via Xcode's Main Thread Checker or Thread.isMainThread asserts).
UX is unchanged for the user.
No @MainActor annotation creep into ReliaBLEManager or its public types is needed to make the Demo compile (this is the regression guard).
A small README / inline comment in the Demo points future consumers at this as the canonical "off-main persistence" pattern.
Not in scope
Adding a background-actor test to the library's own test target — that's covered by the smoke test in Step 5.
Depends on: Step 4 issue (façade Sendable-ization) — but the SwiftData refactor itself can land earlier if it's structured to use .receive(on: DispatchQueue.global()) bridging on the Combine surface for now.
Goal
Refactor the Demo app's SwiftData layer to run on a background actor rather than
@MainActor, so that the Demo itself exercises ReliaBLE across actor boundaries. Catches@MainActor-creep regressions in the library at PR-time and doubles as a concrete pattern for library consumers to copy.Context
See the investigation report at
docs/investigations/swift6-concurrency-audit-2026-05-13.md, Preventive Measures section. Quoting the maintainer's note:Today
CentralViewModelruns on@MainActorand performs SwiftData inserts/upserts directly in Combine.sinkclosures. After the library refactor (#10 and its sub-issues), the manager will be a nonisolatedSendabletype withAsyncStreamevent surfaces. The Demo can exercise this by:ReliaBLEManagerreference on a@MainActorview-model (typical SwiftUI path).@ModelActoror a custom isolatedModelContextwrapper)..task { for await … in manager.peripheralDiscoveries }and hopping into the SwiftData actor for persistence.This is also a useful demonstration for library consumers building real-world apps where BLE event volume warrants off-main persistence.
Deliverable
@ModelActor(or equivalent) wrapper around the SwiftDataModelContainerin the Demo. Suggested name:DeviceStoreActor.@MainActorfor SwiftUI binding (or move to@Observableactor-snapshot pattern if desired).Acceptance criteria
Thread.isMainThreadasserts).@MainActorannotation creep intoReliaBLEManageror its public types is needed to make the Demo compile (this is the regression guard).Not in scope
References
docs/investigations/swift6-concurrency-audit-2026-05-13.mdSendable-ization) — but the SwiftData refactor itself can land earlier if it's structured to use.receive(on: DispatchQueue.global())bridging on the Combine surface for now.