swift-concurrency
Swift Concurrency
Agent Rules
- Analyze
Package.swiftor.pbxprojto determine Swift language mode (5.x vs 6) and toolchain before giving advice. - Before proposing fixes, identify the isolation boundary:
@MainActor, custom actor, actor instance isolation, or nonisolated. - Do not recommend
@MainActoras a blanket fix. Justify why main-actor isolation is correct for the code. - Prefer structured concurrency (child tasks, task groups) over unstructured tasks. Use
Task.detachedonly with a clear reason. - If recommending
@preconcurrency,@unchecked Sendable, ornonisolated(unsafe), require:- a documented safety invariant
- a follow-up ticket to remove or migrate it
- For migration work, optimize for minimal blast radius (small, reviewable changes) and follow the validation loop: Build → Fix errors → Rebuild → Only proceed when clean.
- Course references are for deeper learning only. Use them sparingly and only when they clearly help answer the developer's question.
Triage Checklist (Before Advising)
- Capture the exact compiler diagnostics and the offending symbol(s).
- Identify the current isolation boundary and module defaults (
@MainActor, custom actor, default isolation). - Confirm whether the code is UI-bound or intended to run off the main actor.
Quick Fix Mode (Use When)
Use Quick Fix Mode when:
- The errors are localized (single file or one type) and the isolation boundary is clear.
- The fix does not require API redesign or multi-module changes.
- You can explain the fix in 1–2 steps without changing behavior.
Skip Quick Fix Mode when:
- Default isolation or strict concurrency settings are unknown and likely affect behavior.
- The error crosses module boundaries or involves public API changes.
- The fix would require
@unchecked Sendable,@preconcurrency, ornonisolated(unsafe)without a clear invariant.
Project Settings Intake (Evaluate Before Advising)
Concurrency behavior depends on build settings. Before advising, determine these via Read on Package.swift or Grep in .pbxproj files:
| Setting | SwiftPM (Package.swift) |
Xcode (.pbxproj) |
|---|---|---|
| Default isolation | .defaultIsolation(MainActor.self) |
SWIFT_DEFAULT_ACTOR_ISOLATION |
| Strict concurrency | .enableExperimentalFeature("StrictConcurrency=targeted") |
SWIFT_STRICT_CONCURRENCY |
| Upcoming features | .enableUpcomingFeature("NonisolatedNonsendingByDefault") |
SWIFT_UPCOMING_FEATURE_* |
| Language mode | // swift-tools-version: at top |
Swift Language Version build setting |
If any of these are unknown, ask the developer to confirm them before giving migration-sensitive guidance.
Smallest Safe Fixes (Quick Wins)
Prefer edits that preserve behavior while satisfying data-race safety.
- UI-bound types: isolate the type or specific members to
@MainActor(justify why UI-bound). - Global/static mutable state: move into an
actoror isolate to@MainActorif UI-only. - Background work: for work that should always hop off the caller’s isolation, move expensive work into an
asyncfunction marked@concurrent; for work that doesn’t touch isolated state but can inherit the caller’s isolation (for example withNonisolatedNonsendingByDefault), usenonisolatedwithout@concurrent, or use anactorto guard mutable state. - Sendable errors: prefer immutable/value types; avoid
@unchecked Sendableunless you can prove and document thread safety.
Quick Fix Playbook (Common Diagnostics -> Minimal Fix)
- "Main actor-isolated ... cannot be used from a nonisolated context"
- Quick fix: if UI-bound, make the caller
@MainActoror hop withawait MainActor.run { ... }. - Escalate if this is non-UI code or causes reentrancy; use
references/actors.md.
- Quick fix: if UI-bound, make the caller
- "Actor-isolated type does not conform to protocol"
- Quick fix: add isolated conformance (e.g.,
extension Foo: @MainActor SomeProtocol). - Escalate if the protocol requirements must be
nonisolated; usereferences/actors.md.
- Quick fix: add isolated conformance (e.g.,
- "Sending value of non-Sendable type ... risks causing data races"
- Quick fix: confine access inside an actor or convert to a value type with immutable (
let) state. - Escalate before
@unchecked Sendable; usereferences/sendable.mdandreferences/threading.md.
- Quick fix: confine access inside an actor or convert to a value type with immutable (
- SwiftLint
async_without_await- Quick fix: remove
asyncif not required; if required by protocol/override/@concurrent, use narrow suppression with rationale. Seereferences/linting.md.
- Quick fix: remove
- "wait(...) is unavailable from asynchronous contexts" (XCTest)
- Quick fix: use
await fulfillment(of:)or Swift Testing equivalents. Seereferences/testing.md.
- Quick fix: use
Escalation Path (When Quick Fixes Aren't Enough)
- Gather project settings (default isolation, strict concurrency level, upcoming features).
- Re-evaluate isolation boundaries and which types cross them.
- Use the decision tree + references for the deeper fix.
- If behavior changes are possible, document the invariant and add tests/verification steps.
Quick Decision Tree
When a developer needs concurrency guidance, follow this decision tree:
-
Starting fresh with async code?
- Read
references/async-await-basics.mdfor foundational patterns - For parallel operations →
references/tasks.md(async let, task groups)
- Read
-
Protecting shared mutable state?
- Need to protect class-based state →
references/actors.md(actors, @MainActor) - Need thread-safe value passing →
references/sendable.md(Sendable conformance)
- Need to protect class-based state →
-
Managing async operations?
- Structured async work →
references/tasks.md(Task, child tasks, cancellation) - Streaming data →
references/async-sequences.md(AsyncSequence, AsyncStream)
- Structured async work →
-
Working with legacy frameworks?
- Core Data integration →
references/core-data.md - General migration →
references/migration.md
- Core Data integration →
-
Performance or debugging issues?
- Slow async code →
references/performance.md(profiling, suspension points) - Testing concerns →
references/testing.md(XCTest, Swift Testing)
- Slow async code →
-
Understanding threading behavior?
- Read
references/threading.mdfor thread/task relationship and isolation
- Read
-
Memory issues with tasks?
- Read
references/memory-management.mdfor retain cycle prevention
- Read
Triage-First Playbook (Common Errors -> Next Best Move)
- SwiftLint concurrency-related warnings
- Use
references/linting.mdfor rule intent and preferred fixes; avoid dummy awaits as “fixes”.
- Use
- SwiftLint
async_without_awaitwarning- Remove
asyncif not required; if required by protocol/override/@concurrent, prefer narrow suppression over adding fake awaits. Seereferences/linting.md.
- Remove
- "Sending value of non-Sendable type ... risks causing data races"
- First: identify where the value crosses an isolation boundary
- Then: use
references/sendable.mdandreferences/threading.md(especially Swift 6.2 behavior changes)
- "Main actor-isolated ... cannot be used from a nonisolated context"
- First: decide if it truly belongs on
@MainActor - Then: use
references/actors.md(global actors,nonisolated, isolated parameters) andreferences/threading.md(default isolation)
- First: decide if it truly belongs on
- "Class property 'current' is unavailable from asynchronous contexts" (Thread APIs)
- Use
references/threading.mdto avoid thread-centric debugging and rely on isolation + Instruments
- Use
- "Actor-isolated type does not conform to protocol" (protocol conformance errors)
- First: determine whether the protocol requirements must execute on the actor (for example, UI work on
@MainActor) or can safely benonisolated. - Then: follow the Quick Fix Playbook entry for actor-isolated protocol conformance and
references/actors.mdfor implementation patterns (isolated conformances,nonisolatedrequirements, and escalation steps).
- First: determine whether the protocol requirements must execute on the actor (for example, UI work on
- XCTest async errors like "wait(...) is unavailable from asynchronous contexts"
- Use
references/testing.md(await fulfillment(of:)and Swift Testing patterns)
- Use
- Core Data concurrency warnings/errors
- Use
references/core-data.md(DAO/NSManagedObjectID, default isolation conflicts)
- Use
Core Patterns Reference
Concurrency Tool Selection
| Need | Tool | Key Guidance |
|---|---|---|
| Single async operation | async/await |
Default choice for sequential async work |
| Fixed parallel operations | async let |
Known count at compile time; auto-cancelled on throw |
| Dynamic parallel operations | withTaskGroup |
Unknown count; structured — cancels children on scope exit |
| Sync → async bridge | Task { } |
Inherits actor context; use Task.detached only with documented reason |
| Shared mutable state | actor |
Prefer over locks/queues; keep isolated sections small |
| UI-bound state | @MainActor |
Only for truly UI-related code; justify isolation |
Common Scenarios
Network request with UI update
Task { @concurrent in
let data = try await fetchData()
await MainActor.run { self.updateUI(with: data) }
}
Processing array items in parallel
await withTaskGroup(of: ProcessedItem.self) { group in
for item in items {
group.addTask { await process(item) }
}
for await result in group {
results.append(result)
}
}
Swift 6 Migration Quick Guide
Key changes in Swift 6:
- Strict concurrency checking enabled by default
- Complete data-race safety at compile time
- Sendable requirements enforced on boundaries
- Isolation checking for all async boundaries
Migration Validation Loop
Apply this cycle for each migration change:
- Build — Run
swift buildor Xcode build to surface new diagnostics - Fix — Address one category of error at a time (e.g., all Sendable issues first)
- Rebuild — Confirm the fix compiles cleanly before moving on
- Test — Run the test suite to catch regressions (
swift testor Cmd+U) - Only proceed to the next file/module when all diagnostics are resolved
If a fix introduces new warnings, resolve them before continuing. Never batch multiple unrelated fixes — keep commits small and reviewable.
For detailed migration steps, see references/migration.md.
Reference Files
Load these files as needed for specific topics:
async-await-basics.md- async/await syntax, execution order, async let, URLSession patternstasks.md- Task lifecycle, cancellation, priorities, task groups, structured vs unstructuredthreading.md- Thread/task relationship, suspension points, isolation domains, nonisolatedmemory-management.md- Retain cycles in tasks, memory safety patternsactors.md- Actor isolation, @MainActor, global actors, reentrancy, custom executors, Mutexsendable.md- Sendable conformance, value/reference types, @unchecked, region isolationlinting.md- Concurrency-focused lint rules and SwiftLintasync_without_awaitasync-sequences.md- AsyncSequence, AsyncStream, when to use vs regular async methodscore-data.md- NSManagedObject sendability, custom executors, isolation conflictsperformance.md- Profiling with Instruments, reducing suspension points, execution strategiestesting.md- XCTest async patterns, Swift Testing, concurrency testing utilitiesmigration.md- Swift 6 migration strategy, closure-to-async conversion, @preconcurrency, FRP migration
Verification Checklist (When You Change Concurrency Code)
- Confirm build settings (default isolation, strict concurrency, upcoming features) before interpreting diagnostics.
- Build — Verify the project compiles without new warnings or errors.
- Test — Run tests, especially concurrency-sensitive ones (see
references/testing.md). - Performance — If performance-related, verify with Instruments (see
references/performance.md). - Lifetime — If lifetime-related, verify deinit/cancellation behavior (see
references/memory-management.md). - Check
Task.isCancelledin long-running operations. - Never use semaphores or locks in async contexts — use actors or
Mutexinstead.
Glossary
See references/glossary.md for quick definitions of core concurrency terms used across this skill.
Note: This skill is based on the comprehensive Swift Concurrency Course by Antoine van der Lee.