swift6-concurrency
Swift 6 language mode with complete concurrency checking from the first line of a new Apple-platform project; `MainActor` default isolation per the Xcode 26 template, `Sendable` only where a value cro
SKILL.mdapple-dev-skills/skills/swift6-concurrency/SKILL.md
GitHubSwift 6 / Strict Concurrency
When to invoke
- Starting a new Swift iOS / macOS App project and deciding the language mode.
- Writing
Package.swiftand settingswiftLanguageModes/SWIFT_STRICT_CONCURRENCY. - Adding a new third-party dependency and hitting concurrency warnings / errors.
- User asks "how do I configure Swift 6 / complete concurrency / Sendable / actor /
@preconcurrency".
Default decisions
Swift 6 language mode + complete concurrency checking, applied from the first line of code.
Default actor isolation is
MainActor, following the Xcode 26 App project template (SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor+SWIFT_APPROACHABLE_CONCURRENCY = YES; the SwiftPM equivalent isswiftSettings: [.defaultIsolation(MainActor.self)], which requires// swift-tools-version: 6.2). Under this default an unannotated in-house type never leaves the main actor, so it does not needSendableon its own account —Sendableis required only for a type that crosses intononisolatedcode, an actor, or a module with a different isolation default. Declare it explicitly when it does (struct Foo: Sendableorfinal class Foo: Sendable— the latter only valid when the class is non-inheritable and every stored property is bothSendableandlet; for classes with mutable state, preferactor Fooor@unchecked Sendablewith manual synchronisation).Layer the isolation default per module (see
swiftpm-modularization): keepMainActoras the default for UI/App modules; Engine/Domain modules that are pure logic setswiftSettings: [.defaultIsolation(nil)]so they staynonisolatedby default and avoid needless main-actor hops. Work that must run off the main actor is marked explicitly with@concurrent(SE-0461) rather than left to inference.Protocols that cross actor boundaries (DI injection points) are declared
Sendable, with methodsasync throws. Keep that requirement even when the implementation is an actor: the protocol'sSendableis not what fails when a non-Sendableframework type (AVAssetTrack,VNRequest) is involved — what fails is the value crossing the boundary:Symptom Wrong fix Right fix non-Sendable type 'X' cannot be returned from actor-isolated implementation to caller of protocol requirementDropping : Sendablefrom the protocol — does not silence it (verified on Swift 6.3.2, the same error appears with and without)@preconcurrency importthe framework whose type you don't own, or convert to aSendablevalue type at the actor boundary and never return the framework object itself (preferred — makes the isolation real rather than asserted)For third-party deps that don't support Swift 6 complete checking, in order of preference:
@preconcurrency import Xto isolate the import- Switch packages
- Defer adoption
Rationale
- A greenfield project has no legacy code to migrate, so the pain of complete checking gets amortised — solved once per actor / Sendable as you write — rather than as a one-shot big-bang migration later.
- Swift 6 is the long-term direction of the language; early adoption avoids technical debt.
- Complete checking catches data races at compile time, more than minimal / targeted can.
Deviation considerations
- Migrating existing Swift 5 code: switch to minimal or targeted concurrency checking and upgrade in stages; ramp up one file / module at a time, allowing
@preconcurrencyduring transition. - Significant third-party lag: if a critical dep doesn't support it, put only the target that imports it in Swift 5 language mode (
.swiftLanguageMode(.v5); in Xcode, Swift 5 mode withSWIFT_STRICT_CONCURRENCY = targeted) and move it back to.v6once the dep catches up — don't drop the whole project: in Swift 6 mode strict concurrency is alwayscomplete, so settingtargetedthere does nothing. - Teaching / demo projects: if the goal is to demonstrate older API behaviour, keeping Swift 5 mode is fine.
Related skills
apple-platform-targets: the catalog toolchain is Xcode 26; Swift 6 language mode has been available since Xcode 16, so the language-mode choice does not constrain the deployment floor.swiftpm-modularization:swiftLanguageModes: [.v6]sets the default for the whole package; individual targets can opt down withswiftSettings: [.swiftLanguageMode(.v5)](available since swift-tools-version 6.0) — useful when migrating a legacy dependency without blocking the rest of the package.apple-skills:guide-swift-concurrency(aggregated external): fixing the concurrent code itself — actors, structured concurrency, cancellation, strict-concurrency diagnostics. This skill stops at the build settings and the dependency boundary.apple-skills:swift-concurrency(aggregated external): API reference forasync/await,Task,TaskGroup,AsyncSequence.- Official sources: when verifying or updating a factual or version-sensitive claim, read
references/official-docs.md.