Swift Concurrency expert skill covering async/await, structured concurrency (Task, TaskGroup), actors and @MainActor, Sendable protocol and checking, AsyncSequence/AsyncStream, continuations for bridging callback APIs, Swift 6 strict concurrency mode, and common concurrency patterns (debouncing, throttling, actor-based shared state, background processing). Use this skill whenever the user writes concurrent Swift code, works with async/await, actors, or Sendable, migrates to Swift 6 concurrency, or needs to handle background work and thread safety. Triggers on: async, await, Task, TaskGroup, actor, @MainActor, Sendable, @Sendable, concurrency, AsyncSequence, AsyncStream, continuation, withCheckedContinuation, nonisolated, GlobalActor, Swift 6, strict concurrency, data race, thread safety, background task, parallel, concurrent, dispatch queue migration, or any Swift concurrency question.
Swift Concurrency expert skill covering async/await, structured concurrency (Task, TaskGroup), actors and @MainActor, Sendable protocol and checking, AsyncSequence/AsyncStream, continuations for bridging callback APIs, Swift 6 strict concurrency mode, and common concurrency patterns (debouncing, throttling, actor-based shared state, background processing). Use this skill whenever the user writes concurrent Swift code, works with async/await, actors, or Sendable, migrates to Swift 6 concurrency, or needs to handle background work and thread safety. Triggers on: async, await, Task, TaskGroup, actor, @MainActor, Sendable, @Sendable, concurrency, AsyncSequence, AsyncStream, continuation, withCheckedContinuation, nonisolated, GlobalActor, Swift 6, strict concurrency, data race, thread safety, background task, parallel, concurrent, dispatch queue migration, or any Swift concurrency question.
iOS Concurrency Skill
Core Rules
Use async/await for ALL new asynchronous code. Do not use completion handlers or Combine for async operations in new code.
Use structured concurrency (Task, TaskGroup). Avoid unstructured task leaks. Prefer async let or TaskGroup over spawning loose Task {} blocks.
Mark UI-updating code with @MainActor. SwiftUI views are already @MainActor-isolated. UIKit code that touches UI must run on @MainActor.
Use actors for shared mutable state instead of locks, semaphores, or serial dispatch queues.
All types crossing actor boundaries must be Sendable. The compiler enforces this in strict concurrency mode.
Prefer value types (struct, enum) for Sendable. They are implicitly Sendable when all stored properties are Sendable.
Use withCheckedContinuation to bridge callback-based APIs to async/await. Never resume a continuation more than once.
Use AsyncStream for bridging delegate/callback patterns to AsyncSequence.
Task.detached is rarely needed. Use Task {} with explicit actor isolation instead. Detached tasks lose actor context and priority inheritance.
Always handle Task cancellation cooperatively. Check Task.isCancelled or call try Task.checkCancellation() at appropriate points.
// Enable in Package.swift
.target(
name: "MyTarget",
swiftSettings: [.swiftLanguageMode(.v6)]
)
// Or in Xcode: Build Settings → Swift Language Version → 6// Common fixes:// 1. Non-Sendable type crossing isolation boundary// → Make type Sendable or use @unchecked Sendable// 2. Mutable capture in @Sendable closure// → Use actor or move state inside Task// 3. Global variable not concurrency-safe// → Use actor, nonisolated(unsafe), or make it let// 4. Legacy framework types not Sendable// → @preconcurrency import FrameworkName
Anti-Patterns to Avoid
// BAD: Using Task.detached without good reasonTask.detached {
awaitself.doWork() // loses actor isolation and priority
}
// GOOD: Use Task {} — inherits actor contextTask {
await doWork()
}
// BAD: Blocking an actor with synchronous workactorDataProcessor {
funcprocess(_data: Data) -> Result {
heavySyncComputation(data) // blocks the actor's executor
}
}
// GOOD: Move heavy sync work off the actoractorDataProcessor {
funcprocess(_data: Data) async -> Result {
awaitTask.detached(priority: .utility) {
heavySyncComputation(data) // runs on cooperative pool
}.value
}
}
// BAD: Ignoring cancellationTask {
for item in hugeList {
await process(item) // never checks cancellation
}
}
// GOOD: Cooperative cancellationTask {
for item in hugeList {
tryTask.checkCancellation()
await process(item)
}
}
// BAD: Resuming continuation multiple times (CRASH)
withCheckedContinuation { continuation in
api.fetch { data in
continuation.resume(returning: data)
}
api.fetch { data in// second resume — CRASH
continuation.resume(returning: data)
}
}
// BAD: Never resuming continuation (LEAK — task hangs forever)
withCheckedContinuation { continuation in
api.fetch { data iniflet data = data {
continuation.resume(returning: data)
}
// if data is nil, continuation is never resumed!
}
}
Performance Considerations
Task creation overhead: ~1-2 microseconds. Do not create tasks in tight loops for trivial work.
Actor contention: If many tasks await the same actor, they serialize. Keep actor methods fast.
MainActor bottleneck: Do not run heavy computation on @MainActor. Offload to a non-isolated async function or Task.detached.
async let: Creates a child task immediately. Only use when you actually need parallelism.
TaskGroup: Prefer over multiple async let when the number of operations is dynamic.
Sendable checking: Zero runtime cost. It is compile-time only.
When NOT to use async/await: Pure synchronous computation, simple property access, performance-critical inner loops.