dpearson2699/swift-ios-skills9 files

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(nonsending), or Task.immediate; when designing actor-based architectures, structured concurrency with TaskGroup, or background work offloading; or when migrating from @preconcurrency to full Swift 6 strict concurrency.

Specification
Skill ID
dpearson2699/swift-ios-skills/swift-concurrency
Publisher
dpearson2699
Repository
swift-ios-skills
Installs
0
Files
9
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

dpearson2699/swift-ios-skills/swift-concurrencyInstalls these files
  • SKILL.md
  • evals/evals.json
  • references/approachable-concurrency.md
  • references/async-algorithms.md
  • references/bridging-interop.md
  • references/concurrency-patterns.md
  • references/diagnostics.md
  • references/swiftui-concurrency.md
  • references/synchronization-primitives.md

What this skill tells the agent

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
  • Swift 6.2 Language Changes
  • Actor Isolation Rules
  • Sendable Rules
  • Structured Concurrency Patterns
  • Task Cancellation
  • Actor Reentrancy
  • AsyncSequence and AsyncStream
  • `@Observable and Concurrency`
  • Synchronization Primitives
  • Common Mistakes
  • Review Checklist
  • 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.

SituationRecommended fix
UI-bound typeAnnotate the type or relevant members with @MainActor.
Protocol conformance on MainActor typeUse an isolated conformance: extension Foo: @MainActor Proto.
Global / static stateProtect with @MainActor or move into an actor.
Background work neededUse a @concurrent async function on a nonisolated type.
Sendable errorPrefer immutable value types. Add Sendable only when correct.
Cross-isolation callbackUse 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.

// With default MainActor isolation enabled, these are implicitly @MainActor:
final class StickerLibrary {
    static let shared = StickerLibrary()  // safe -- on MainActor
    var stickers: [Sticker] = []
}

final class StickerModel {
    let photoProcessor = PhotoProcessor()
    var selection: [PhotosPickerItem] = []
}

// Conformances are also implicitly isolated:
extension StickerModel: Exportable {
    func export() {
        photoProcessor.exportAsPNG()
    }
}

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.

class PhotoProcessor {
    func extractSticker(data: Data, with id: String?) async -> Sticker? {
        // In Swift 6.2+, this runs on the caller's actor (e.g., MainActor)
        // instead of hopping to a background thread.
        // ...
    }
}

@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
        }
        // No data race -- photoProcessor stays on MainActor
        return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
    }
}

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.

class 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
    }

    @concurrent
    static func extractSubject(from data: Data) async -> Sticker {
        // Expensive image processing -- runs on background thread pool
        // ...
    }
}

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.
nonisolated struct PhotoProcessor {
    @concurrent
    func process(data: Data) async -> ProcessedPhoto? { /* ... */ }
}

// Caller:
processedPhotos[item.id] = await PhotoProcessor().process(data: data)

SE-0472: Task.immediate

Task.immediate starts executing synchronously on the current actor before any suspension point, rather than being enqueued.

Task.immediate { await handleUserInput() }

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.

for await _ in Observations { model.count } {
    print("Count changed to \(model.count)")
}

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.

protocol Exportable {
    func export()
}

// Isolated conformance: only usable on MainActor
extension StickerModel: @MainActor Exportable {
    func export() {
        photoProcessor.exportAsPNG()
    }
}

@MainActor
struct ImageExporter {
    var items: [any Exportable]

    mutating func add(_ item: StickerModel) {
        items.append(item)  // OK -- ImageExporter is on MainActor
    }
}

Use this skill in a project

Open any RiverX project, search the Skills panel for dpearson2699/swift-ios-skills/swift-concurrency, and install it in one click.

Use this skill