Telemetry is cross-cutting. A Tracker gets passed into view models, services, background workers, network layers — anything that might want to record an event. In Swift 6, anything shared across concurrency boundaries (tasks, actors, @Sendable closures) must be Sendable. Today Tracker isn't, so every consumer either fights the compiler with workarounds (@unchecked Sendable wrappers, nonisolated(unsafe), captured-var hacks) or simply can't adopt strict concurrency.
Making Tracker Sendable removes that friction once, at the source, so every downstream library and feature gets safe concurrent telemetry for free.
When a feature team turns on Swift 6 / strict concurrency, the first wall they hit is usually telemetry:
Task {
tracker.track(MyEvent()) // ❌ capture of non-Sendable 'Tracker' in @Sendable closure
}Workarounds we've seen across the codebase:
@unchecked Sendablewrappers aroundTracker.nonisolated(unsafe) let tracker.- Forcing the call site onto
@MainActoreven when there's no UI reason to. - Avoiding
Task/asyncentirely and reaching for Combine just to dodge the warning.
Each is a local patch that hides a real concurrency question and adds noise.
Tracker (and its supporting types — Detail, Event, Baggage, the recording closure) become Sendable. The internals use Mutex / Lock to make the mutable state (baggage, child trackers) safe to touch from any isolation domain. The public API is unchanged in shape — same track(_:), same composition operators — it just stops requiring escape hatches.
- Unblocks Swift 6 adoption everywhere downstream. Telemetry sits at the bottom of the dependency graph; until it's Sendable, no library above it can cleanly turn on strict concurrency. Fixing it once unblocks dozens of consumers.
- Removes a class of subtle bugs. Captured-
varpatterns in test code (var count = 0; tracker { _ in count += 1 }) and in production (background closures mutating shared state) are data races the compiler currently can't see. Sendable forces them into safe shapes (Lock, recorder types). - Better testing story. Sendable-friendly
EventRecorder/DetailRecorderreplace ad-hoc captured-var counters in tests, giving cleaner, race-free assertions. - Aligns with Apple's direction. Swift 6 strict concurrency is the default-on future. Foundation, SwiftUI, and the platform SDKs are all moving this way; libraries that don't follow become friction points.
- Cheap to consume. For most callers it's a no-op recompile. For callers using the hacky patterns above, it's a small mechanical refactor to a safer equivalent — and we do that work in this rollout, not later under pressure.
- One coordinated 2.0 release of
mobile-swift-telemetryand its integration packages (Dynatrace, Adobe, New Relic, etc.). - A handful of small downstream PRs to swap captured-var test patterns for
EventRecorder/DetailRecorder. Already done in BankingKit, ConfigurationKit, OptimizelyFlagging, Sparks; pattern is well established. - No behavioural change at runtime — it's a type-system / safety improvement.
We can either fix telemetry once at the source, or every team fixes it locally with @unchecked Sendable and lives with the data races. The first option is cheaper, safer, and a one-time cost.