/swift-concurrency
Guide for building, auditing, and refactoring Swift code using modern concurrency patterns (Swift 6+). This skill should be used when working with async/await, Tasks, actors, MainActor, Sendable types, isolation domains, or when migrating legacy callback/Combine code to
$ npx -y skills add jamesrochabrun/skills --skill swift-concurrency --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/swift-concurrency
Context preview
The summary Claude sees to decide when to auto-load this skill.
Guide for building, auditing, and refactoring Swift code using modern concurrency patterns (Swift 6+). This skill should be used when working with async/await, Tasks, actors, MainActor, Sendable types, isolation domains, or when migrating legacy callback/Combine code to
SKILL.md
swift-concurrency.SKILL.mdname: swift-concurrency
description: Guide for building, auditing, and refactoring Swift code using modern concurrency patterns (Swift 6+). This skill should be used when working with async/await, Tasks, actors, MainActor, Sendable types, isolation domains, or when migrating legacy callback/Combine code to structured concurrency. Covers Approachable Concurrency settings, isolated parameters, and common pitfalls.
Swift Concurrency
Overview
This skill provides guidance for writing thread-safe Swift code using modern concurrency patterns. It covers three main workflows: building new async code, auditing existing code for issues, and refactoring legacy patterns to Swift 6+.
**Core principle**: Isolation is inherited by default. With Approachable Concurrency, code starts on MainActor and propagates through the program automatically. Opt out explicitly when needed.
Workflow Decision Tree
What are you doing?
│
├─► BUILDING new async code
│ └─► See "Building Workflow" below
│
├─► AUDITING existing code
│ └─► See "Auditing Checklist" below
│
└─► REFACTORING legacy code
└─► See "Refactoring Workflow" belowBuilding Workflow
When writing new async code, follow this decision process:
Step 1: Determine Isolation Needs
Does this type manage UI state or interact with UI?
│
├─► YES → Mark with @MainActor
│
└─► NO → Does it have mutable state shared across contexts?
│
├─► YES → Consider: Can it live on MainActor anyway?
│ │
│ ├─► YES → Use @MainActor (simpler)
│ │
│ └─► NO → Use a custom actor (requires justification)
│
└─► NO → Leave non-isolated (default with Approachable Concurrency)Step 2: Design Async Functions
// PREFER: Inherit caller's isolation (works everywhere)
func fetchData(isolation: isolated (any Actor)? = #isolation) async throws -> Data {
// Runs on whatever actor the caller is on
}
// USE WHEN: CPU-intensive work that must run in background
@concurrent
func processLargeFile() async -> Result { }
// AVOID: Non-isolated async without explicit choice
func ambiguousAsync() async { } // Where does this run?Step 3: Handle Parallel Work
// For known number of independent operations
async let avatar = fetchImage("avatar.jpg")
async let banner = fetchImage("banner.jpg")
let (a, b) = await (avatar, banner)
// For dynamic number of operations
try await withThrowingTaskGroup(of: Void.self) { group in
for id in userIDs {
group.addTask { try await fetchUser(id) }
}
try await group.waitForAll()
}Step 4: SwiftUI Integration
struct ProfileView: View {
@State private var avatar: Image?
var body: some View {
avatar
.task { avatar = await downloadAvatar() } // Auto-cancels on disappear
.task(id: userID) { /* Reloads when userID changes */ }
}
}
// For user actions
Button("Save") {
Task { await saveProfile() } // Inherits MainActor isolation
}Auditing Checklist
When reviewing Swift concurrency code, check for these issues:
Critical Issues (Must Fix)
- [ ] **Blocking the cooperative pool**: Look for `DispatchSemaphore.wait()`, `DispatchGroup.wait()`, or similar blocking calls inside async contexts
- [ ] **Data races**: Non-Sendable types crossing isolation boundaries without proper handling
- [ ] **Non-isolated async in non-Sendable types**: These only work from non-isolated contexts
Common Issues (Should Fix)
- [ ] **Actor overuse**: Custom actors without justification (see "Actor Justification Test" in references)
- [ ] **Unnecessary `MainActor.run`**: Should usually be `@MainActor` on the function instead
- [ ] **Thinking async = background**: Synchronous CPU work inside async functions still blocks
- [ ] **Unstructured Tasks where structured works**: `Task { }` instead of `async let` or `TaskGroup`
- [ ] **Missing cancellation handling**: Long operations should check `Task.isCancelled`
SwiftUI-Specific
- [ ] **Views not MainActor-isolated**: SwiftUI views should be `@MainActor` (or use `@Observable`)
- [ ] **Accessing @State from detached tasks**: Must hop back to MainActor
Sendable Compliance
- [ ] **@unchecked Sendable overuse**: Should be rare and justified
- [ ] **Making everything Sendable**: Not all types need to cross boundaries
- [ ] **Non-Sendable closures escaping**: Check closure captures
Refactoring Workflow
From Callbacks to async/await
// BEFORE: Callback-based
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, error in
if let error { completion(.failure(error)); return }
// ...
}.resume()
}
// AFTER: async/await with continuation
func fetchUser(id: Int) async throws -> User {
try await withCheckedThrowingContinuation { continuation in
fetchUser(id: id) { result in
continuation.resume(with: result)
}
}
}From DispatchQueue to Actors
// BEFORE: Queue-based protection
class BankAccount {
private let queue = DispatchQueue(label: "account")
private var _balance: Double = 0
var balance: Double {
queue.sync { _balance }
}
func deposit(_ amount: Double) {
queue.async { self._balance += amount }
}
}
// AFTER: Actor (if truly needs own isolation)
actor BankAccount {
var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
}
// BETTER: MainActor class (if doesn't need concurrent access)
@MainActor
class BankAccount {
var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
}From Combine to AsyncSequence
// BEFORE: Combine publisher
cancellable = NotificationCenter.default
.publisher(for: .userDidLogin)
.sink { notification in /* ... */ }
// AFTER: AsyncSequence
for await _ in NotificationCenter.default.notifications(named: .userDidLogin) {
// Handle notificaRead more
name: swift-concurrency description: Guide for building, auditing, and refactoring Swift code using modern concurrency patterns (Swift 6+). This skill should be used when working with async/await, Tasks, actors, MainActor, Sendable types, isolation domains, or when migrating legacy callback/Combine code to structured concurrency. Covers Approachable Concurrency settings, isolated parameters, and common pitfalls.
Swift Concurrency
Overview
This skill provides guidance for writing thread-safe Swift code using modern concurrency patterns. It covers three main workflows: building new async code, auditing existing code for issues, and refactoring legacy patterns to Swift 6+.
**Core principle**: Isolation is inherited by default. With Approachable Concurrency, code starts on MainActor and propagates through the program automatically. Opt out explicitly when needed.
Workflow Decision Tree
What are you doing?
│
├─► BUILDING new async code
│ └─► See "Building Workflow" below
│
├─► AUDITING existing code
│ └─► See "Auditing Checklist" below
│
└─► REFACTORING legacy code
└─► See "Refactoring Workflow" belowBuilding Workflow
When writing new async code, follow this decision process:
Step 1: Determine Isolation Needs
Does this type manage UI state or interact with UI?
│
├─► YES → Mark with @MainActor
│
└─► NO → Does it have mutable state shared across contexts?
│
├─► YES → Consider: Can it live on MainActor anyway?
│ │
│ ├─► YES → Use @MainActor (simpler)
│ │
│ └─► NO → Use a custom actor (requires justification)
│
└─► NO → Leave non-isolated (default with Approachable Concurrency)Step 2: Design Async Functions
// PREFER: Inherit caller's isolation (works everywhere)
func fetchData(isolation: isolated (any Actor)? = #isolation) async throws -> Data {
// Runs on whatever actor the caller is on
}
// USE WHEN: CPU-intensive work that must run in background
@concurrent
func processLargeFile() async -> Result { }
// AVOID: Non-isolated async without explicit choice
func ambiguousAsync() async { } // Where does this run?Step 3: Handle Parallel Work
// For known number of independent operations
async let avatar = fetchImage("avatar.jpg")
async let banner = fetchImage("banner.jpg")
let (a, b) = await (avatar, banner)
// For dynamic number of operations
try await withThrowingTaskGroup(of: Void.self) { group in
for id in userIDs {
group.addTask { try await fetchUser(id) }
}
try await group.waitForAll()
}Step 4: SwiftUI Integration
struct ProfileView: View {
@State private var avatar: Image?
var body: some View {
avatar
.task { avatar = await downloadAvatar() } // Auto-cancels on disappear
.task(id: userID) { /* Reloads when userID changes */ }
}
}
// For user actions
Button("Save") {
Task { await saveProfile() } // Inherits MainActor isolation
}Auditing Checklist
When reviewing Swift concurrency code, check for these issues:
Critical Issues (Must Fix)
- [ ] **Blocking the cooperative pool**: Look for `DispatchSemaphore.wait()`, `DispatchGroup.wait()`, or similar blocking calls inside async contexts
- [ ] **Data races**: Non-Sendable types crossing isolation boundaries without proper handling
- [ ] **Non-isolated async in non-Sendable types**: These only work from non-isolated contexts
Common Issues (Should Fix)
- [ ] **Actor overuse**: Custom actors without justification (see "Actor Justification Test" in references)
- [ ] **Unnecessary `MainActor.run`**: Should usually be `@MainActor` on the function instead
- [ ] **Thinking async = background**: Synchronous CPU work inside async functions still blocks
- [ ] **Unstructured Tasks where structured works**: `Task { }` instead of `async let` or `TaskGroup`
- [ ] **Missing cancellation handling**: Long operations should check `Task.isCancelled`
SwiftUI-Specific
- [ ] **Views not MainActor-isolated**: SwiftUI views should be `@MainActor` (or use `@Observable`)
- [ ] **Accessing @State from detached tasks**: Must hop back to MainActor
Sendable Compliance
- [ ] **@unchecked Sendable overuse**: Should be rare and justified
- [ ] **Making everything Sendable**: Not all types need to cross boundaries
- [ ] **Non-Sendable closures escaping**: Check closure captures
Refactoring Workflow
From Callbacks to async/await
// BEFORE: Callback-based
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, error in
if let error { completion(.failure(error)); return }
// ...
}.resume()
}
// AFTER: async/await with continuation
func fetchUser(id: Int) async throws -> User {
try await withCheckedThrowingContinuation { continuation in
fetchUser(id: id) { result in
continuation.resume(with: result)
}
}
}From DispatchQueue to Actors
// BEFORE: Queue-based protection
class BankAccount {
private let queue = DispatchQueue(label: "account")
private var _balance: Double = 0
var balance: Double {
queue.sync { _balance }
}
func deposit(_ amount: Double) {
queue.async { self._balance += amount }
}
}
// AFTER: Actor (if truly needs own isolation)
actor BankAccount {
var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
}
// BETTER: MainActor class (if doesn't need concurrent access)
@MainActor
class BankAccount {
var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
}From Combine to AsyncSequence
// BEFORE: Combine publisher
cancellable = NotificationCenter.default
.publisher(for: .userDidLogin)
.sink { notification in /* ... */ }
// AFTER: AsyncSequence
for await _ in NotificationCenter.default.notifications(named: .userDidLogin) {
// Handle notificaA comprehensive plugin and marketplace for Claude Code containing 24 custom skills across engineering, Apple development, product management, design, content, trading, database, QA, educational, and AI architecture domains.
Repo: jamesrochabrun/skills
Other skills on jamesrochabrun-skills.
- /anthropic-architect
Determine the best Anthropic architecture for your project by analyzing requirements and recommending the optimal combination of Skills, Agents, Prompts, and SDK primitives.
Open skill - /anthropic-prompt-engineer
Master Anthropic's prompt engineering techniques to generate new prompts or improve existing ones using best practices for Claude AI models.
Open skill - /apple-hig-designer
Design iOS apps following Apple's Human Interface Guidelines. Generate native components, validate designs, and ensure accessibility compliance for iPhone, iPad, and Apple Watch.
Open skill - /book-illustrator
Expert children's book illustrator guide with 2024-2025 best practices, focusing on age-appropriate styles, color theory, character design, and visual storytelling for kids books that captivate young readers.
Open skill - /content-brief-generator
Generate comprehensive content briefs for writers, ensuring clarity, alignment, and strategic content creation across all formats.
Open skill - /design-brief-generator
Generate comprehensive design briefs for design projects. Use this skill when designers ask to "create a design brief", "structure a design project", "define design requirements", or need help planning design work.
Open skill

