From 721a2b2840614b0616ac08d87ebbfc09a48b1abf Mon Sep 17 00:00:00 2001 From: Okmin Date: Thu, 19 Feb 2026 12:01:29 +0900 Subject: [PATCH] feat(skills): add swift-concurrency-6-2 skill for Approachable Concurrency Add skill covering Swift 6.2 Approachable Concurrency model including single-threaded defaults, @concurrent for explicit background offloading, isolated conformances, and MainActor default inference mode. --- skills/swift-concurrency-6-2/SKILL.md | 213 ++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 skills/swift-concurrency-6-2/SKILL.md diff --git a/skills/swift-concurrency-6-2/SKILL.md b/skills/swift-concurrency-6-2/SKILL.md new file mode 100644 index 00000000..7def68b2 --- /dev/null +++ b/skills/swift-concurrency-6-2/SKILL.md @@ -0,0 +1,213 @@ +--- +name: swift-concurrency-6-2 +description: Swift 6.2 Approachable Concurrency — single-threaded by default, @concurrent for explicit background offloading, isolated conformances for main actor types. +--- + +# Swift 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 +// 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 +// 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: + +```swift +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: + +```swift +// 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 +// 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: + +```swift +// 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`: + +```swift +nonisolated struct PhotoProcessor { + 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 +processedPhotos[item.id] = await PhotoProcessor().process(data: data) +``` + +To use `@concurrent`: +1. Mark the containing type as `nonisolated` +2. Add `@concurrent` to the function +3. Add `async` if not already asynchronous +4. Add `await` at call sites + +## 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 | +| `@concurrent` explicit opt-in | Background execution is a deliberate performance choice, not accidental | +| MainActor default inference | Reduces boilerplate `@MainActor` annotations for app targets | +| Opt-in adoption | Non-breaking migration path — enable features incrementally | + +## Migration Steps + +1. **Enable in Xcode**: Swift Compiler > Concurrency section in Build Settings +2. **Enable in SPM**: Use `SwiftSettings` API in package manifest +3. **Use migration tooling**: Automatic code changes via swift.org/migration +4. **Start with MainActor defaults**: Enable inference mode for app targets +5. **Add `@concurrent` where needed**: Profile first, then offload hot paths +6. **Test thoroughly**: Data-race issues become compile-time errors + +## Best Practices + +- **Start on MainActor** — write single-threaded code first, optimize later +- **Use `@concurrent` only for CPU-intensive work** — image processing, compression, complex computation +- **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 `nonisolated` workarounds or `@Sendable` wrappers +- **Migrate incrementally** — enable features one at a time in build settings + +## Anti-Patterns to Avoid + +- Applying `@concurrent` to every async function (most don't need background execution) +- Using `nonisolated` to suppress compiler errors without understanding isolation +- Keeping legacy `DispatchQueue` patterns when actors provide the same safety +- Skipping `model.availability` checks in concurrency-related Foundation Models code +- 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