From 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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/swift-concurrency:swift-concurrencyThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Before proposing a fix:
references/_index.mdreferences/actors.mdreferences/async-algorithms.mdreferences/async-await-basics.mdreferences/async-sequences.mdreferences/core-data.mdreferences/glossary.mdreferences/linting.mdreferences/memory-management.mdreferences/migration.mdreferences/observable.mdreferences/performance.mdreferences/sendable.mdreferences/tasks.mdreferences/testing.mdreferences/threading.mdBefore proposing a fix:
Package.swift or .pbxproj to determine Swift language mode, strict concurrency level, default isolation, and upcoming features. Do this always, not only for migration work.@MainActor, custom actor, actor instance isolation, or nonisolated.@Observable is 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:
@MainActor as a blanket fix. Justify why the code is truly UI-bound.Task.detached only with a clear reason.@preconcurrency, @unchecked Sendable, or nonisolated(unsafe), require a documented safety invariant and a follow-up removal plan.@Observable is involved, understand that it does NOT automatically imply @MainActor isolation - the developer must explicitly opt in. Do not conflate the two.Use Quick Fix Mode when all of these are true:
Skip Quick Fix Mode when any of these are true:
| 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 |
Prefer changes that preserve behavior while satisfying data-race safety:
@MainActor.actor, or use @MainActor only if the state is UI-owned.async API marked @concurrent; when work can safely inherit caller isolation, use nonisolated without @concurrent.@unchecked Sendable.@MainActor when it drives UI; use nonisolated for computed properties or methods that don't touch UI state.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.
// ✅ 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
}
}
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)
}
}
}
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)
}
}
// ❌ 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.
| 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 |
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)
}
}
Key changes in Swift 6:
Apply this cycle for each migration change:
swift build or Xcode build to surface new diagnosticsswift test or Cmd+U)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.
Open the smallest reference that matches the question:
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 behaviorreferences/observable.md - @Observable + @MainActor patterns, SwiftUI integration, async access, migration from ObservableObjectreferences/async-sequences.md - AsyncSequence, AsyncStream, when to use vs regular async methodsreferences/async-algorithms.md - Debounce, throttle, merge, combineLatest, channels, timersreferences/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 conflictsreferences/migration.md - Swift 6 migration strategy, closure-to-async conversion, @preconcurrency, FRP migrationreferences/linting.md - Concurrency-focused lint rules and SwiftLint async_without_awaitreferences/glossary.md - Quick definitions of core concurrency termsWhen changing concurrency code:
Task.isCancelled in long-running operations.Mutex would express ownership more safely.@Observable, verify that UI-driving properties are accessed on @MainActor.Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub pszypowicz/claude-skills --plugin swift-concurrency