Swift Concurrency
Agent Rules
- Analyze
Package.swift or .pbxproj to 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
@MainActor as 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.detached only with a clear reason.
- If recommending
@preconcurrency, @unchecked Sendable, or nonisolated(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, or nonisolated(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
actor or isolate to @MainActor if UI-only.
- Background work: for work that should always hop off the caller’s isolation, move expensive work into an
async function marked @concurrent; for work that doesn’t touch isolated state but can inherit the caller’s isolation (for example with NonisolatedNonsendingByDefault), use nonisolated without @concurrent, or use an actor to guard mutable state.
- Sendable errors: prefer immutable/value types; avoid
@unchecked Sendable unless 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
@MainActor or hop with await MainActor.run { ... }.
- Escalate if this is non-UI code or causes reentrancy; use
references/actors.md.
- "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; use references/actors.md.
- "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; use references/sendable.md and references/threading.md.
- SwiftLint
async_without_await
- Quick fix: remove
async if not required; if required by protocol/override/@concurrent, use narrow suppression with rationale. See references/linting.md.
- "wait(...) is unavailable from asynchronous contexts" (XCTest)
- Quick fix: use
await fulfillment(of:) or Swift Testing equivalents. See references/testing.md.
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.md for foundational patterns
- For parallel operations →
references/tasks.md (async let, task groups)
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)
Managing async operations?
- Structured async work →
references/tasks.md (Task, child tasks, cancellation)
- Streaming data →
references/async-sequences.md (AsyncSequence, AsyncStream)
Working with legacy frameworks?
- Core Data integration →
references/core-data.md
- General migration →
references/migration.md
Performance or debugging issues?
- Slow async code →
references/performance.md (profiling, suspension points)
- Testing concerns →
references/testing.md (XCTest, Swift Testing)
Understanding threading behavior?
- Read
references/threading.md for thread/task relationship and isolation
Memory issues with tasks?
- Read
references/memory-management.md for retain cycle prevention
Triage-First Playbook (Common Errors -> Next Best Move)
- SwiftLint concurrency-related warnings
- Use
references/linting.md for rule intent and preferred fixes; avoid dummy awaits as “fixes”.
- SwiftLint
async_without_await warning
- Remove
async if not required; if required by protocol/override/@concurrent, prefer narrow suppression over adding fake awaits. See references/linting.md.
- "Sending value of non-Sendable type ... risks causing data races"
- First: identify where the value crosses an isolation boundary
- Then: use
references/sendable.md and references/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) and references/threading.md (default isolation)
- "Class property 'current' is unavailable from asynchronous contexts" (Thread APIs)
- Use
references/threading.md to avoid thread-centric debugging and rely on isolation + Instruments
- "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 be nonisolated.
- Then: follow the Quick Fix Playbook entry for actor-isolated protocol conformance and
references/actors.md for implementation patterns (isolated conformances, nonisolated requirements, and escalation steps).
- XCTest async errors like "wait(...) is unavailable from asynchronous contexts"
- Use
references/testing.md (await fulfillment(of:) and Swift Testing patterns)
- Core Data concurrency warnings/errors
- Use
references/core-data.md (DAO/NSManagedObjectID, default isolation conflicts)
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 build or 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 test or 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 patterns
tasks.md - Task lifecycle, cancellation, priorities, task groups, structured vs unstructured
threading.md - Thread/task relationship, suspension points, isolation domains, nonisolated
memory-management.md - Retain cycles in tasks, memory safety patterns
actors.md - Actor isolation, @MainActor, global actors, reentrancy, custom executors, Mutex
sendable.md - Sendable conformance, value/reference types, @unchecked, region isolation
linting.md - Concurrency-focused lint rules and SwiftLint async_without_await
async-sequences.md - AsyncSequence, AsyncStream, when to use vs regular async methods
core-data.md - NSManagedObject sendability, custom executors, isolation conflicts
performance.md - Profiling with Instruments, reducing suspension points, execution strategies
testing.md - XCTest async patterns, Swift Testing, concurrency testing utilities
migration.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.isCancelled in long-running operations.
- Never use semaphores or locks in async contexts — use actors or
Mutex instead.
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.
1---2name: swift-concurrency3description: Diagnose data races, convert callback-based code to async/await, implement actor isolation patterns, resolve Sendable conformance issues, and guide Swift 6 migration. Use when developers mention: (1) Swift Concurrency, async/await, actors, or tasks, (2) "use Swift Concurrency" or "modern concurrency patterns", (3) migrating to Swift 6, (4) data races or thread safety issues, (5) refactoring closures to async/await, (6) @MainActor, Sendable, or actor isolation, (7) concurrent code architecture or performance optimization, (8) concurrency-related linter warnings (SwiftLint or similar; e.g. async_without_await, Sendable/actor isolation/MainActor lint).4---5# Swift Concurrency67## Agent Rules891. Analyze `Package.swift` or `.pbxproj` to determine Swift language mode (5.x vs 6) and toolchain before giving advice.102. Before proposing fixes, identify the isolation boundary: `@MainActor`, custom actor, actor instance isolation, or nonisolated.113. Do not recommend `@MainActor` as a blanket fix. Justify why main-actor isolation is correct for the code.124. Prefer structured concurrency (child tasks, task groups) over unstructured tasks. Use `Task.detached` only with a clear reason.135. If recommending `@preconcurrency`, `@unchecked Sendable`, or `nonisolated(unsafe)`, require:14 - a documented safety invariant15 - a follow-up ticket to remove or migrate it166. For migration work, optimize for minimal blast radius (small, reviewable changes) and follow the validation loop: **Build → Fix errors → Rebuild → Only proceed when clean**.177. Course references are for deeper learning only. Use them sparingly and only when they clearly help answer the developer's question.1819## Triage Checklist (Before Advising)2021- Capture the exact compiler diagnostics and the offending symbol(s).22- Identify the current isolation boundary and module defaults (`@MainActor`, custom actor, default isolation).23- Confirm whether the code is UI-bound or intended to run off the main actor.2425## Quick Fix Mode (Use When)2627Use Quick Fix Mode when:28- The errors are localized (single file or one type) and the isolation boundary is clear.29- The fix does not require API redesign or multi-module changes.30- You can explain the fix in 1–2 steps without changing behavior.3132Skip Quick Fix Mode when:33- Default isolation or strict concurrency settings are unknown and likely affect behavior.34- The error crosses module boundaries or involves public API changes.35- The fix would require `@unchecked Sendable`, `@preconcurrency`, or `nonisolated(unsafe)` without a clear invariant.3637## Project Settings Intake (Evaluate Before Advising)3839Concurrency behavior depends on build settings. Before advising, determine these via `Read` on `Package.swift` or `Grep` in `.pbxproj` files:4041| Setting | SwiftPM (`Package.swift`) | Xcode (`.pbxproj`) |42|---------|--------------------------|---------------------|43| Default isolation | `.defaultIsolation(MainActor.self)` | `SWIFT_DEFAULT_ACTOR_ISOLATION` |44| Strict concurrency | `.enableExperimentalFeature("StrictConcurrency=targeted")` | `SWIFT_STRICT_CONCURRENCY` |45| Upcoming features | `.enableUpcomingFeature("NonisolatedNonsendingByDefault")` | `SWIFT_UPCOMING_FEATURE_*` |46| Language mode | `// swift-tools-version:` at top | Swift Language Version build setting |4748If any of these are unknown, ask the developer to confirm them before giving migration-sensitive guidance.4950## Smallest Safe Fixes (Quick Wins)5152Prefer edits that preserve behavior while satisfying data-race safety.5354- **UI-bound types**: isolate the type or specific members to `@MainActor` (justify why UI-bound).55- **Global/static mutable state**: move into an `actor` or isolate to `@MainActor` if UI-only.56- **Background work**: for work that should always hop off the caller’s isolation, move expensive work into an `async` function marked `@concurrent`; for work that doesn’t touch isolated state but can inherit the caller’s isolation (for example with `NonisolatedNonsendingByDefault`), use `nonisolated` without `@concurrent`, or use an `actor` to guard mutable state.57- **Sendable errors**: prefer immutable/value types; avoid `@unchecked Sendable` unless you can prove and document thread safety.5859## Quick Fix Playbook (Common Diagnostics -> Minimal Fix)6061- **"Main actor-isolated ... cannot be used from a nonisolated context"**62 - Quick fix: if UI-bound, make the caller `@MainActor` or hop with `await MainActor.run { ... }`.63 - Escalate if this is non-UI code or causes reentrancy; use `references/actors.md`.64- **"Actor-isolated type does not conform to protocol"**65 - Quick fix: add isolated conformance (e.g., `extension Foo: @MainActor SomeProtocol`).66 - Escalate if the protocol requirements must be `nonisolated`; use `references/actors.md`.67- **"Sending value of non-Sendable type ... risks causing data races"**68 - Quick fix: confine access inside an actor or convert to a value type with immutable (`let`) state.69 - Escalate before `@unchecked Sendable`; use `references/sendable.md` and `references/threading.md`.70- **SwiftLint `async_without_await`**71 - Quick fix: remove `async` if not required; if required by protocol/override/@concurrent, use narrow suppression with rationale. See `references/linting.md`.72- **"wait(...) is unavailable from asynchronous contexts" (XCTest)**73 - Quick fix: use `await fulfillment(of:)` or Swift Testing equivalents. See `references/testing.md`.7475## Escalation Path (When Quick Fixes Aren't Enough)76771. Gather project settings (default isolation, strict concurrency level, upcoming features).782. Re-evaluate isolation boundaries and which types cross them.793. Use the decision tree + references for the deeper fix.804. If behavior changes are possible, document the invariant and add tests/verification steps.8182## Quick Decision Tree8384When a developer needs concurrency guidance, follow this decision tree:85861. **Starting fresh with async code?**87 - Read `references/async-await-basics.md` for foundational patterns88 - For parallel operations → `references/tasks.md` (async let, task groups)89902. **Protecting shared mutable state?**91 - Need to protect class-based state → `references/actors.md` (actors, @MainActor)92 - Need thread-safe value passing → `references/sendable.md` (Sendable conformance)93943. **Managing async operations?**95 - Structured async work → `references/tasks.md` (Task, child tasks, cancellation)96 - Streaming data → `references/async-sequences.md` (AsyncSequence, AsyncStream)97984. **Working with legacy frameworks?**99 - Core Data integration → `references/core-data.md`100 - General migration → `references/migration.md`1011025. **Performance or debugging issues?**103 - Slow async code → `references/performance.md` (profiling, suspension points)104 - Testing concerns → `references/testing.md` (XCTest, Swift Testing)1051066. **Understanding threading behavior?**107 - Read `references/threading.md` for thread/task relationship and isolation1081097. **Memory issues with tasks?**110 - Read `references/memory-management.md` for retain cycle prevention111112## Triage-First Playbook (Common Errors -> Next Best Move)113114- SwiftLint concurrency-related warnings115 - Use `references/linting.md` for rule intent and preferred fixes; avoid dummy awaits as “fixes”.116- SwiftLint `async_without_await` warning117 - Remove `async` if not required; if required by protocol/override/@concurrent, prefer narrow suppression over adding fake awaits. See `references/linting.md`.118- "Sending value of non-Sendable type ... risks causing data races"119 - First: identify where the value crosses an isolation boundary120 - Then: use `references/sendable.md` and `references/threading.md` (especially Swift 6.2 behavior changes)121- "Main actor-isolated ... cannot be used from a nonisolated context"122 - First: decide if it truly belongs on `@MainActor`123 - Then: use `references/actors.md` (global actors, `nonisolated`, isolated parameters) and `references/threading.md` (default isolation)124- "Class property 'current' is unavailable from asynchronous contexts" (Thread APIs)125 - Use `references/threading.md` to avoid thread-centric debugging and rely on isolation + Instruments126- "Actor-isolated type does not conform to protocol" (protocol conformance errors)127 - First: determine whether the protocol requirements must execute on the actor (for example, UI work on `@MainActor`) or can safely be `nonisolated`.128 - Then: follow the Quick Fix Playbook entry for actor-isolated protocol conformance and `references/actors.md` for implementation patterns (isolated conformances, `nonisolated` requirements, and escalation steps).129- XCTest async errors like "wait(...) is unavailable from asynchronous contexts"130 - Use `references/testing.md` (`await fulfillment(of:)` and Swift Testing patterns)131- Core Data concurrency warnings/errors132 - Use `references/core-data.md` (DAO/`NSManagedObjectID`, default isolation conflicts)133134## Core Patterns Reference135136### Concurrency Tool Selection137138| Need | Tool | Key Guidance |139|------|------|-------------|140| Single async operation | `async/await` | Default choice for sequential async work |141| Fixed parallel operations | `async let` | Known count at compile time; auto-cancelled on throw |142| Dynamic parallel operations | `withTaskGroup` | Unknown count; structured — cancels children on scope exit |143| Sync → async bridge | `Task { }` | Inherits actor context; use `Task.detached` only with documented reason |144| Shared mutable state | `actor` | Prefer over locks/queues; keep isolated sections small |145| UI-bound state | `@MainActor` | Only for truly UI-related code; justify isolation |146147### Common Scenarios148149**Network request with UI update**150```swift151Task { @concurrent in152 let data = try await fetchData()153 await MainActor.run { self.updateUI(with: data) }154}155```156157**Processing array items in parallel**158```swift159await withTaskGroup(of: ProcessedItem.self) { group in160 for item in items {161 group.addTask { await process(item) }162 }163 for await result in group {164 results.append(result)165 }166}167```168169## Swift 6 Migration Quick Guide170171Key changes in Swift 6:172- **Strict concurrency checking** enabled by default173- **Complete data-race safety** at compile time174- **Sendable requirements** enforced on boundaries175- **Isolation checking** for all async boundaries176177### Migration Validation Loop178179Apply this cycle for each migration change:1801811. **Build** — Run `swift build` or Xcode build to surface new diagnostics1822. **Fix** — Address one category of error at a time (e.g., all Sendable issues first)1833. **Rebuild** — Confirm the fix compiles cleanly before moving on1844. **Test** — Run the test suite to catch regressions (`swift test` or Cmd+U)1855. **Only proceed** to the next file/module when all diagnostics are resolved186187If a fix introduces new warnings, resolve them before continuing. Never batch multiple unrelated fixes — keep commits small and reviewable.188189For detailed migration steps, see `references/migration.md`.190191## Reference Files192193Load these files as needed for specific topics:194195- **`async-await-basics.md`** - async/await syntax, execution order, async let, URLSession patterns196- **`tasks.md`** - Task lifecycle, cancellation, priorities, task groups, structured vs unstructured197- **`threading.md`** - Thread/task relationship, suspension points, isolation domains, nonisolated198- **`memory-management.md`** - Retain cycles in tasks, memory safety patterns199- **`actors.md`** - Actor isolation, @MainActor, global actors, reentrancy, custom executors, Mutex200- **`sendable.md`** - Sendable conformance, value/reference types, @unchecked, region isolation201- **`linting.md`** - Concurrency-focused lint rules and SwiftLint `async_without_await`202- **`async-sequences.md`** - AsyncSequence, AsyncStream, when to use vs regular async methods203- **`core-data.md`** - NSManagedObject sendability, custom executors, isolation conflicts204- **`performance.md`** - Profiling with Instruments, reducing suspension points, execution strategies205- **`testing.md`** - XCTest async patterns, Swift Testing, concurrency testing utilities206- **`migration.md`** - Swift 6 migration strategy, closure-to-async conversion, @preconcurrency, FRP migration207208## Verification Checklist (When You Change Concurrency Code)2092101. Confirm build settings (default isolation, strict concurrency, upcoming features) before interpreting diagnostics.2112. **Build** — Verify the project compiles without new warnings or errors.2123. **Test** — Run tests, especially concurrency-sensitive ones (see `references/testing.md`).2134. **Performance** — If performance-related, verify with Instruments (see `references/performance.md`).2145. **Lifetime** — If lifetime-related, verify deinit/cancellation behavior (see `references/memory-management.md`).2156. Check `Task.isCancelled` in long-running operations.2167. Never use semaphores or locks in async contexts — use actors or `Mutex` instead.217218## Glossary219220See `references/glossary.md` for quick definitions of core concurrency terms used across this skill.221222---223224**Note**: This skill is based on the comprehensive [Swift Concurrency Course](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=skill-footer) by Antoine van der Lee.