swift-concurrency

Resolve Swift concurrency compiler errors, adopt approachable concurrency (SE-0466), and write data-race-safe async code. Use when fixing Sendable conformance errors, actor isolation warnings, or strict concurrency diagnostics; when adopting default MainActor isolation, @concurrent, nonisolated(nons

By dpearson2699 · 3,653 installs

npx skills add dpearson2699/swift-ios-skills --skill swift-concurrency

Source repository · Upstream listing

Swift Concurrency Review, fix, and write concurrent Swift code targeting Swift 6.3+. Gate Swift 6.4 / Xcode 27 beta cleanup APIs behind explicit toolchain and availability checks. Apply actor isolation, Sendable safety, and modern concurrency patterns with minimal behavior changes. Contents [Triage Workflow]( triage workflow) [Swift 6.2 Language Changes]( swift 62 language changes) [Actor Isolation Rules]( actor isolation rules) [Sendable Rules]( sendable rules) [Structured Concurrency Patterns]( structured concurrency patterns) [Task Cancellation]( task cancellation) [Actor Reentrancy]( actor reentrancy) [AsyncSequence and AsyncStream]( asyncsequence and asyncstream) [ @Observable and Concurrency ]( observable and concurrency) [Synchronization Primitives]( synchronization primitives) [Common Mistakes]( common mistakes) [Review Checklist]( review checklist) [References]( references) Triage Workflow When diagnosing a concurrency issue, follow this sequence: Step 1: Capture context Copy the exact compiler diagnostic(s) and the offending symbol(s). Identify the project's concurrency settings: Swift language version (must be 6.2+). Xcode/toolchain version for version specific features and release note workarounds. Whether Approachable Concurrency is enabled. Whether Default Actor Isolation is set to MainActor . Swift 6 strict concurrency status: complete/errors in Swift 6 language mode; Complete / Targeted / Minimal only when auditing Swift 5 migration settings. Determine the current actor context of the code ( @MainActor , custom actor , nonisolated ) and whether a default isolation mode is active. Confirm whether the code is UI bound or intended to run off the main actor. Step 2: Apply the smallest safe fix Prefer edits that preserve existing behavior while satisfying data race safety. Situation Recommended fix UI bound type Annotate the type or relevant members with @MainActor . Protocol conformance on MainActor type Use an isolated conformance: extension Foo: @MainActor Proto . Global / static state Protect with @MainActor or move into an actor. Background work needed Use a @concurrent async function on a nonisolated type. Sendable error Prefer immutable value types. Add Sendable only when correct. Cross isolation callback Use sending parameters (SE 0430) for finer control. Step 3: Verify Rebuild and confirm the diagnostic is resolved. Check for new warnings introduced by the fix. Ensure no unnecessary @unchecked Sendable or nonisolated(unsafe) was added. For build setting reviews, stop at settings plus the smallest code level remediation. Do not add Thread Sanitizer, broad migration ordering, or architecture advice unless the prompt asks for diagnostics or migration. Swift 6.2 Language Changes Swift 6.2 introduces "approachable concurrency" a set of language changes that make concurrent code safer by default while reducing annotation burden. In Xcode, Approachable Concurrency and Default Actor Isolation are separate build settings: use Approachable Concurrency for the bundled upcoming feature flags, and set Default Actor Isolation to MainActor when you want unannotated code inferred as @MainActor . SE 0466: Default MainActor Isolation With the default isolation MainActor compiler flag, SwiftPM .defaultIsolation(MainActor.self) , or Xcode's Default Actor Isolation setting set to MainActor , unannotated declarations in the module are inferred as @MainActor unless explicitly opted out. Effect: Eliminates most data race safety errors for UI bound code and global/static state without writing @MainActor everywhere. When to use: Recommended for apps, scripts, and other executable targets where most code is UI bound. Not recommended for library targets that should remain actor agnostic. SE 0461: nonisolated(nonsending) Nonisolated async functions now stay on the caller's actor by default instead of hopping to the global concurrent executor. This is the nonisolated(nonsending) behavior. Use @concurrent to explicitly request background execution when needed. @concurrent Attribute @concurrent ensures a function always runs on the concurrent thread pool, freeing the calling actor to run other tasks. To move a function to a background thread, show both opt outs together: 1. Ensure the containing type is nonisolated or the function can be called from a nonisolated context. 2. Add @concurrent to the offloaded function. nonisolated alone does not move CPU heavy work off the caller's actor. 3. Add async if not already asynchronous. 4. Add await at call sites. SE 0472: Task.immediate Task.immediate starts executing synchronously on the current actor before any suspension point, rather than being enqueued. Use for latency sensitive work that should begin without delay. There is also Task.immediateDetached which combines immediate start with detached semantics. SE 0475: Transactional Observation (Observations) Observations { } provides async observation of @Observable types via AsyncSequence , enabling transactional change tracking. Isolated Conformances A conformance that needs MainActor state is called an isolated conformance . The compiler ensures it is only used in a matching isolation context. If ImageExporter were nonisolated , adding a StickerModel would fail: "Main actor isolated conformance of 'StickerModel' to 'Exportable' cannot be used in nonisolated context." Clock Epochs ContinuousClock and SuspendingClock now expose .epoch (SE 0473), enabling instant comparison and conversion between clock types. Actor Isolation Rules 1. All mutable shared state MUST be protected by an actor or global actor. 2. @MainActor for all UI touching code. No exceptions. Global actors are actor isolation; use @MainActor as the standard pattern for UI bound shared state. 3. Use nonisolated only for methods that access immutable ( let ) properties or are pure computations. 4. Use @concurrent to explicitly move work off the caller's actor. 5. Never use nonisolated(unsafe) unless you have proven data race safety and exhausted safer alternatives. It is an unsafe audit boundary, not a synchronization primitive. For the narrow unsafe pointer capture pattern in a synchronous parallel loop, follow the proof requirements in [bridging and interop](references/bridging interop.md synchronous parallel for concurrentperform versus task groups). Disjoint per iteration access can eliminate conflicting concurrent access and the need for synchronization; disjointness is not itself synchronization. 6. Never add manual locks ( NSLock , DispatchSemaphore ) inside actors. Sendable Rules 1. Value types (structs, enums) are automatically Sendable when all stored properties are Sendable . For diagnostics on mutable reference types, first extract an immutable Sendable value snapshot or DTO instead of sharing the reference. 2. Actors are implicitly Sendable . 3. @MainActor classes are implicitly Sendable . Do NOT add redundant Sendable conformance. 4. Non actor classes: must be final with all stored properties let and Sendable . 5. @unchecked Sendable is a last resort. Document why the compiler cannot prove safety. 6. Use sending parameters (SE 0430) for finer grained isolation control. 7. Use @preconcurrency import only for third party libraries you cannot modify. Plan to remove it. Structured Concurrency Patterns Async Defer (Swift 6.4+) defer blocks in async contexts can contain await in Swift 6.4+ (SE 0493). Use for async cleanup: closing connections, flushing buffers, or releasing resources that require an async call. The defer body inherits the surrounding isolation and is implicitly awaited at scope exit. It does not suppress cancellation; cleanup that checks Task.isCancelled or Task.checkCancellation() still observes cancellation. Task: Unstructured, inherits caller context. Task.detached: No inherited context. Use only when you explicitly need to break isolation inheritance. Task.immediate: Starts immediately on current actor. Use for latency sensitive work. async let: Fixed number of concurrent operations. TaskGroup: Dynamic number of concurrent operations. Task Cancellation Cancellation is cooperative. Check Task.isCancelled or call try Task.checkCancellation() in loops. Use .task modifier in SwiftUI it handles cancellation on view disappear. Use withTaskCancellationHandler for cleanup. Swift 6.4 / iOS 27+ beta: use withTaskCancellationShield only for short cleanup or rollback that must complete after cancellation. Inside the shield, Task.isCancelled is false and Task.checkCancellation() does not throw; cancellation is observable again after the scope exits. Cancel stored tasks in deinit or onDisappear . Actor Reentrancy Actors are reentrant. State can change across suspension points. AsyncSequence and AsyncStream Use AsyncStream to bridge callback/delegate APIs: Use withCheckedContinuation / withCheckedThrowingContinuation for single value callbacks. Resume exactly once. @Observable and Concurrency @Observable classes should be @MainActor for view models. Use @State to own an @Observable instance (replaces @StateObject ). Use Observations { } (SE 0475) for async observation of @Observable properties as an AsyncSequence . Synchronization Primitives When actors are not the right fit — synchronous access, performance critical paths, or bridging C/ObjC — use low level synchronization primitives: Actors remain the default for async shared state when callers can suspend; they give compiler enforced isolation and structured concurrency integration, but outside calls are async actor hops, require reentrancy care across await , and do not fit synchronous C callbacks. Use global actors such as @MainActor for UI bound shared state; never use nonisolated(unsafe) as a synchronization substitute. Mutex<Value (iOS 18+, Synchronization module): Preferred lock for new code. Stores protected state inside the lock. withLock { } pattern. OSAllocatedUnfairLock (iOS 16+, os module): Use when targeting older iOS versions. Supports ownership assertions for debugging. Atomic<Value (iOS 18+, Synchronization module): Lock free atomics for independent counters and flags. Atomic is Sendable and can be stored in Sendable holder types. Use .relaxed only for standalone metrics; use acquire/release ordering or a lock when coordinating other data. Key rule: Never put locks inside actors (double synchronization), and never hold a lock across await (blocks a thread through suspension and can starve the cooperative pool or deadlock). See [references/synchronization primitives.md](references/synchronization primitives.md) for full API details, code examples, and a decision guide for choosing locks vs actors. Mutex.withLock and OSAllocatedUnfairLock.withLock use synchronous closures; that API shape is what keeps critical sections non suspending. Gate Mutex and Atomic with runtime if available(iOS 18, ) , never if swift(...) or platform compile time checks. For NSLock , correct only the false Sendable premise and avoid explaining conformance mechanics. If a legacy lock wrapper truly needs @unchecked Sendable , name the invariant: all mutable state is private, all access uses one lock, no mutable references escape, and no lock is held across await . Common Mistakes 1. Blocking the main actor. Heavy computation on @MainActor freezes UI. Move to a @concurrent function. 2. Unnecessary @MainActor. Network layers, data processing, and model code do not need @