Swift concurrency
Skill pszypowicz/claude-skills/plugins/swift-concurrency/skills/swift-concurrency
Diagnose data races, convert callback-based code to async/await, implement actor isolation patterns, resolve Sendable conformance issues, fix @Observable + @MainActor interaction problems, 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) @Observable with concurrency or actor isolation, (8) concurrent code architecture or performance optimization, (9) concurrency-related linter warnings (SwiftLint or similar), (10) SwiftUI view model patterns with Observation framework.From its SKILL.md
npx -y skills add pszypowicz/claude-skills --skill swift-concurrencyAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
13.1 KB, ~2.9k tokens by cl100k_base, as published. Nobody here has run it
Swift Concurrency
Fast Path
Before proposing a fix:
- Analyze
Package.swiftor.pbxprojto determine Swift language mode, strict concurrency level, default isolation, and upcoming features. Do this always, not only for migration work. - Capture the exact diagnostic and offending symbol.
- Determine the isolation boundary:
@MainActor, custom actor, actor instance isolation, ornonisolated. - Confirm whether the code is UI-bound or intended to run off the main actor.
- Check if
@Observableis in play - it changes how isolation inference works on the type.
Project settings that change concurrency behavior:
| Setting | SwiftPM (Package.swift) | Xcode (.pbxproj) |
|---|---|---|
| Language mode | swiftLanguageVersions or -swift-version (// swift-tools-version: is not a reliable proxy) | Swift Language Version |
| Strict concurrency | .enableExperimentalFeature("StrictConcurrency=targeted") | SWIFT_STRICT_CONCURRENCY |
| Default isolation | .defaultIsolation(MainActor.self) | SWIFT_DEFAULT_ACTOR_ISOLATION |
| Upcoming features | .enableUpcomingFeature("NonisolatedNonsendingByDefault") | SWIFT_UPCOMING_FEATURE_* |
If any of these are unknown, ask the developer to confirm them before giving migration-sensitive guidance. Do not guess.
Guardrails:
- Do not recommend
@MainActoras a blanket fix. Justify why the code is truly UI-bound. - Prefer structured concurrency over unstructured tasks. Use
Task.detachedonly with a clear reason. - If recommending
@preconcurrency,@unchecked Sendable, ornonisolated(unsafe), require a documented safety invariant and a follow-up removal plan. - Optimize for the smallest safe change. Do not refactor unrelated architecture during migration.
- When
@Observableis involved, understand that it does NOT automatically imply@MainActorisolation - the developer must explicitly opt in. Do not conflate the two.
Quick Fix Mode
Use Quick Fix Mode when all of these are true:
- The issue is localized to one file or one type.
- The isolation boundary is clear.
- The fix can be explained in 1-2 behavior-preserving steps.
Skip Quick Fix Mode when any of these are true:
- Build settings or default isolation are unknown.
- The issue crosses module boundaries or changes public API behavior.
- The likely fix depends on unsafe escape hatches.
Common Diagnostics
| Diagnostic | First check | Smallest safe fix | Escalate to |
|---|---|---|---|
Main actor-isolated ... cannot be used from a nonisolated context | Is this truly UI-bound? | Isolate the caller to @MainActor or use await MainActor.run { ... } only when main-actor ownership is correct. | references/actors.md, references/threading.md |
Actor-isolated type does not conform to protocol | Must the requirement run on the actor? | Prefer isolated conformance (e.g., extension Foo: @MainActor SomeProtocol); use nonisolated only for truly nonisolated requirements. | references/actors.md |
Sending value of non-Sendable type ... risks causing data races | What isolation boundary is being crossed? | Keep access inside one actor, or convert the transferred value to an immutable/value type. | references/sendable.md, references/threading.md |
@Observable class with @MainActor property issues | Is the entire class UI-bound, or just some properties? | Apply @MainActor to the whole class if it's a view model; use nonisolated for properties that don't need main actor. | references/observable.md |
@Observable + async access from background | Are you mutating observed properties off the main actor? | Route mutations through await MainActor.run {} or isolate the class to @MainActor. | references/observable.md |
SwiftLint async_without_await | Is async actually required by protocol, override, or @concurrent? | Remove async, or use a narrow suppression with rationale. Never add fake awaits. | references/linting.md |
wait(...) is unavailable from asynchronous contexts | Is this legacy XCTest async waiting? | Replace with await fulfillment(of:) or Swift Testing equivalents. | references/testing.md |
| Core Data concurrency warnings | Are NSManagedObject instances crossing contexts or actors? | Pass NSManagedObjectID or map to a Sendable value type. | references/core-data.md |
Thread.current unavailable from asynchronous contexts | Are you debugging by thread instead of isolation? | Reason in terms of isolation and use Instruments/debugger instead. | references/threading.md |
| SwiftLint concurrency-related warnings | Which specific lint rule triggered? | Use references/linting.md for rule intent and preferred fixes; avoid dummy awaits. | references/linting.md |
When Quick Fixes Fail
- Gather project settings if not already confirmed.
- Re-evaluate which isolation boundaries the type crosses.
- Route to the matching reference file for a deeper fix.
- If the fix may change behavior, document the invariant and add verification steps.
Smallest Safe Fixes
Prefer changes that preserve behavior while satisfying data-race safety:
- UI-bound state: isolate the type or member to
@MainActor. - Shared mutable state: move it behind an
actor, or use@MainActoronly if the state is UI-owned. - Background work: when work must hop off caller isolation, use an
asyncAPI marked@concurrent; when work can safely inherit caller isolation, usenonisolatedwithout@concurrent. - Sendability issues: prefer immutable values and explicit boundaries over
@unchecked Sendable. - @Observable view models: isolate the entire class to
@MainActorwhen it drives UI; usenonisolatedfor computed properties or methods that don't touch UI state.
@Observable + @MainActor Quick Guide
This is one of the most common pain points in modern SwiftUI apps. The @Observable macro does NOT infer @MainActor - you must explicitly add it when the class drives UI.
The Core Pattern
// ✅ Correct: @MainActor view model with @Observable
@MainActor @Observable
final class ContentViewModel {
var items: [Item] = []
var isLoading = false
func loadItems() async {
isLoading = true
items = await APIClient.fetchItems()
isLoading = false
}
}
SwiftUI Integration (Observation framework)
Use @State (not @StateObject) and @Environment (not @EnvironmentObject) with @Observable:
// ✅ Correct Observation-era SwiftUI
struct ContentView: View {
@State private var viewModel = ContentViewModel()
var body: some View {
List(viewModel.items) { item in
Text(item.name)
}
.task { await viewModel.loadItems() }
}
}
// ✅ Passing via environment
@main
struct MyApp: App {
@State private var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environment(library)
}
}
}
struct LibraryView: View {
@Environment(Library.self) private var library
var body: some View {
List(library.books) { book in
BookView(book: book)
}
}
}
Bindings with @Bindable
For two-way bindings to @Observable objects, use @Bindable:
struct EditView: View {
@Bindable var viewModel: ContentViewModel
var body: some View {
TextField("Name", text: $viewModel.name)
}
}
Common Mistakes
// ❌ Missing @MainActor - mutations from async contexts may happen off main thread
@Observable
final class ViewModel {
var items: [Item] = [] // SwiftUI reads this - must be main-actor-isolated
}
// ❌ Using old ObservableObject patterns with @Observable
@Observable
final class ViewModel: ObservableObject { // Don't conform to both
@Published var items: [Item] = [] // @Published is for ObservableObject
}
// ❌ Using @StateObject with @Observable
struct MyView: View {
@StateObject var vm = ViewModel() // Use @State instead
}
For the full guide, see references/observable.md.
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 |
| Observable view model | @MainActor @Observable | For SwiftUI-driving models; use @State not @StateObject |
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 Router
Open the smallest reference that matches the question:
- Foundations
references/async-await-basics.md- async/await syntax, execution order, async let, URLSession patternsreferences/tasks.md- Task lifecycle, cancellation, priorities, task groups, structured vs unstructuredreferences/actors.md- Actor isolation, @MainActor, global actors, reentrancy, custom executors, Mutexreferences/sendable.md- Sendable conformance, value/reference types, @unchecked, region isolationreferences/threading.md- Execution model, suspension points, Swift 6.2 isolation behavior
- Observation
references/observable.md- @Observable + @MainActor patterns, SwiftUI integration, async access, migration from ObservableObject
- Streams
references/async-sequences.md- AsyncSequence, AsyncStream, when to use vs regular async methodsreferences/async-algorithms.md- Debounce, throttle, merge, combineLatest, channels, timers
- Applied topics
references/testing.md- Swift Testing first, XCTest fallback, leak checksreferences/performance.md- Profiling with Instruments, reducing suspension points, execution strategiesreferences/memory-management.md- Retain cycles in tasks, memory safety patternsreferences/core-data.md- NSManagedObject sendability, custom executors, isolation conflicts
- Migration and tooling
references/migration.md- Swift 6 migration strategy, closure-to-async conversion, @preconcurrency, FRP migrationreferences/linting.md- Concurrency-focused lint rules and SwiftLintasync_without_await
- Glossary
references/glossary.md- Quick definitions of core concurrency terms
Verification Checklist
When changing concurrency code:
- Re-check build settings before interpreting diagnostics.
- Build and clear one category of errors before moving on. Do not batch unrelated fixes into the same change.
- Run tests, especially actor-, lifetime-, and cancellation-sensitive tests.
- Use Instruments for performance claims instead of guessing.
- Verify deallocation and cancellation behavior for long-lived tasks.
- Check
Task.isCancelledin long-running operations. - Never use semaphores or ad hoc locking in async contexts when actor isolation or
Mutexwould express ownership more safely. - When using
@Observable, verify that UI-driving properties are accessed on@MainActor.
What ships with it: 16 files
204.0 KB alongside SKILL.md
references/
- actors.md16.9 KB
- async-algorithms.md7.5 KB
- async-await-basics.md13.9 KB
- async-sequences.md9.1 KB
- core-data.md10.5 KB
- glossary.md7.8 KB
- _index.md4.1 KB
- linting.md6.0 KB
- memory-management.md9.4 KB
- migration.md31.4 KB
- observable.md15.2 KB
- performance.md8.2 KB
- sendable.md17.0 KB
- tasks.md18.0 KB
- testing.md8.0 KB
- threading.md21.0 KB