Skillshub swift-concurrency-6-2
Swift 6.2 Approachable Concurrency — single-threaded by default, @concurrent for explicit background offloading, isolated conformances for main actor types.
git clone https://github.com/ComeOnOliver/skillshub
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/affaan-m/everything-claude-code/swift-concurrency-6-2" ~/.claude/skills/comeonoliver-skillshub-swift-concurrency-6-2 && rm -rf "$T"
skills/affaan-m/everything-claude-code/swift-concurrency-6-2/SKILL.mdSwift 6.2 Approachable Concurrency
Patterns for adopting Swift 6.2's concurrency model where code runs single-threaded by default and concurrency is introduced explicitly. Eliminates common data-race errors without sacrificing performance.
When to Activate
- Migrating Swift 5.x or 6.0/6.1 projects to Swift 6.2
- Resolving data-race safety compiler errors
- Designing MainActor-based app architecture
- Offloading CPU-intensive work to background threads
- Implementing protocol conformances on MainActor-isolated types
- Enabling Approachable Concurrency build settings in Xcode 26
Core Problem: Implicit Background Offloading
In Swift 6.1 and earlier, async functions could be implicitly offloaded to background threads, causing data-race errors even in seemingly safe code:
// Swift 6.1: ERROR @MainActor final class StickerModel { let photoProcessor = PhotoProcessor() func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? { guard let data = try await item.loadTransferable(type: Data.self) else { return nil } // Error: Sending 'self.photoProcessor' risks causing data races return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier) } }
Swift 6.2 fixes this: async functions stay on the calling actor by default.
// Swift 6.2: OK — async stays on MainActor, no data race @MainActor final class StickerModel { let photoProcessor = PhotoProcessor() func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? { guard let data = try await item.loadTransferable(type: Data.self) else { return nil } return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier) } }
Core Pattern — Isolated Conformances
MainActor types can now conform to non-isolated protocols safely:
protocol Exportable { func export() } // Swift 6.1: ERROR — crosses into main actor-isolated code // Swift 6.2: OK with isolated conformance extension StickerModel: @MainActor Exportable { func export() { photoProcessor.exportAsPNG() } }
The compiler ensures the conformance is only used on the main actor:
// OK — ImageExporter is also @MainActor @MainActor struct ImageExporter { var items: [any Exportable] mutating func add(_ item: StickerModel) { items.append(item) // Safe: same actor isolation } } // ERROR — nonisolated context can't use MainActor conformance nonisolated struct ImageExporter { var items: [any Exportable] mutating func add(_ item: StickerModel) { items.append(item) // Error: Main actor-isolated conformance cannot be used here } }
Core Pattern — Global and Static Variables
Protect global/static state with MainActor:
// Swift 6.1: ERROR — non-Sendable type may have shared mutable state final class StickerLibrary { static let shared: StickerLibrary = .init() // Error } // Fix: Annotate with @MainActor @MainActor final class StickerLibrary { static let shared: StickerLibrary = .init() // OK }
MainActor Default Inference Mode
Swift 6.2 introduces a mode where MainActor is inferred by default — no manual annotations needed:
// With MainActor default inference enabled: final class StickerLibrary { static let shared: StickerLibrary = .init() // Implicitly @MainActor } final class StickerModel { let photoProcessor: PhotoProcessor var selection: [PhotosPickerItem] // Implicitly @MainActor } extension StickerModel: Exportable { // Implicitly @MainActor conformance func export() { photoProcessor.exportAsPNG() } }
This mode is opt-in and recommended for apps, scripts, and other executable targets.
Core Pattern — @concurrent for Background Work
When you need actual parallelism, explicitly offload with
@concurrent:
Important: This example requires Approachable Concurrency build settings — SE-0466 (MainActor default isolation) and SE-0461 (NonisolatedNonsendingByDefault). With these enabled,
stays on the caller's actor, making mutable state access safe. Without these settings, this code has a data race — the compiler will flag it.extractSticker
nonisolated final class PhotoProcessor { private var cachedStickers: [String: Sticker] = [:] func extractSticker(data: Data, with id: String) async -> Sticker { if let sticker = cachedStickers[id] { return sticker } let sticker = await Self.extractSubject(from: data) cachedStickers[id] = sticker return sticker } // Offload expensive work to concurrent thread pool @concurrent static func extractSubject(from data: Data) async -> Sticker { /* ... */ } } // Callers must await let processor = PhotoProcessor() processedPhotos[item.id] = await processor.extractSticker(data: data, with: item.id)
To use
@concurrent:
- Mark the containing type as
nonisolated - Add
to the function@concurrent - Add
if not already asynchronousasync - Add
at call sitesawait
Key Design Decisions
| Decision | Rationale |
|---|---|
| Single-threaded by default | Most natural code is data-race free; concurrency is opt-in |
| Async stays on calling actor | Eliminates implicit offloading that caused data-race errors |
| Isolated conformances | MainActor types can conform to protocols without unsafe workarounds |
explicit opt-in | Background execution is a deliberate performance choice, not accidental |
| MainActor default inference | Reduces boilerplate annotations for app targets |
| Opt-in adoption | Non-breaking migration path — enable features incrementally |
Migration Steps
- Enable in Xcode: Swift Compiler > Concurrency section in Build Settings
- Enable in SPM: Use
API in package manifestSwiftSettings - Use migration tooling: Automatic code changes via swift.org/migration
- Start with MainActor defaults: Enable inference mode for app targets
- Add
where needed: Profile first, then offload hot paths@concurrent - Test thoroughly: Data-race issues become compile-time errors
Best Practices
- Start on MainActor — write single-threaded code first, optimize later
- Use
only for CPU-intensive work — image processing, compression, complex computation@concurrent - Enable MainActor inference mode for app targets that are mostly single-threaded
- Profile before offloading — use Instruments to find actual bottlenecks
- Protect globals with MainActor — global/static mutable state needs actor isolation
- Use isolated conformances instead of
workarounds ornonisolated
wrappers@Sendable - Migrate incrementally — enable features one at a time in build settings
Anti-Patterns to Avoid
- Applying
to every async function (most don't need background execution)@concurrent - Using
to suppress compiler errors without understanding isolationnonisolated - Keeping legacy
patterns when actors provide the same safetyDispatchQueue - Skipping
checks in concurrency-related Foundation Models codemodel.availability - Fighting the compiler — if it reports a data race, the code has a real concurrency issue
- Assuming all async code runs in the background (Swift 6.2 default: stays on calling actor)
When to Use
- All new Swift 6.2+ projects (Approachable Concurrency is the recommended default)
- Migrating existing apps from Swift 5.x or 6.0/6.1 concurrency
- Resolving data-race safety compiler errors during Xcode 26 adoption
- Building MainActor-centric app architectures (most UI apps)
- Performance optimization — offloading specific heavy computations to background