Strict Concurrency in Swift 6: Benefits, Drawbacks, and Incremental Adoption

מפת עולם צבעונית עם מספרים דינמיים ותחושת תנועה חזקה

Modern applications increasingly rely on asynchronous and multithreaded operations. With Swift 6, the platform introduced the Strict Concurrency flag—a compile-time safety mechanism for parallel code. In this article, we’ll explore:

  1. What Strict Concurrency is
  2. Advantages of Strict Concurrency
  3. Drawbacks of Strict Concurrency
  4. When and why to enable strict checking
  5. Step-by-step migration to “complete” mode
  6. Final recommendations

1. What Is Strict Concurrency

Strict Concurrency is a compiler mode where any potentially unsafe parallel access to data is blocked at build time. To pass a value into a “thread-safe” context (for example, into an asynchronous task or a @Sendable closure), its type must conform to the Sendable protocol. If you attempt to mutate an actor-isolated property (for instance, one marked @MainActor) from a non-isolated context, the compiler will emit an error. Likewise, any unsynchronized access to global or static variables is flagged as a violation.

2. Advantages of Strict Concurrency

Compile-time safety guarantees
You catch data races and parallel-access errors during compilation, not at runtime or in production.

Clear state isolation
Actors and the @MainActor attribute clearly delineate which code runs on which thread or queue, making debugging and reasoning about code easier.

Consistent callback rules
All closures marked @Sendable are automatically checked by the compiler for capturing non-Sendable values, removing the need for manual verification.

Toolchain integration
Xcode highlights problematic code, and Thread Sanitizer adds dynamic analysis, giving you both static and runtime protection against data races.

Self-documenting code
Explicit annotations (@MainActor, @unchecked Sendable) serve as built-in documentation, helping new team members understand the concurrency model.

3. Drawbacks of Strict Concurrency

Steep migration curve for existing code
Legacy UIKit projects often require extensive annotations, extensions like extension MyType: @unchecked Sendable { }, and logic refactoring—an investment of weeks or months.

Increased code noise
Large codebases can become cluttered with repetitive Sendable conformances and MainActor.run { … } wrappers, reducing readability.

Workarounds weaken guarantees
Sometimes you must disable checks for certain sections with @unchecked Sendable or @preconcurrency, which undermines the strict safety guarantees.

False positives in early compiler versions
Occasionally the compiler cannot prove that code is safe even when it is, forcing you to refactor or disable checks in specific parts.

4. When and Why to Enable Strict Checking

For new Swift 6 projects, enable complete mode from the start to avoid technical debt. In existing codebases, consider strict mode when:

  • You’re working on critical modules (payments, personal data) where race conditions have serious consequences.

  • Your team has the time and resources to invest in migration and values deterministic concurrency.

  • You’re rolling out new features or screens, letting them adopt strict checks from day one.

5. Step-by-Step Migration to “Complete” Mode

Step 1: Switch to “warnings”
In Build Settings or via SwiftPM (.strict-concurrency=warnings), enable warnings so the compiler reports where your code violates strict rules.

Step 2: Tackle by responsibility area
First, address UI and ViewModel code by marking classes and methods with @MainActor. Next, audit all closures to ensure each @Sendable closure only captures Sendable types or safe copies of data. Finally, wrap global and static variables in a custom @globalActor.

Step 3: Enable “complete”
Once most warnings are resolved, flip the switch to complete mode. The compiler will now treat any remaining violations as errors, enforcing full compliance.

Step 4: Run Thread Sanitizer
Keep Thread Sanitizer enabled alongside static checks to catch any data races that slip through compile-time verification.

6. Final Recommendations

With a thoughtful, phased approach, Strict Concurrency transforms from a daunting obstacle into a robust framework for safe multithreading. For new modules, enable strict checks immediately. In existing code, methodically apply actor annotations and Sendable conformances. This process prevents elusive bugs, reduces debugging effort, and makes your app’s architecture more transparent and maintainable.


Share

More posts

Send us a message