From ios-developer
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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ios-developer:swift-concurrencyThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
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+.
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.
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" below
When writing new async code, follow this decision process:
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)
// 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?
// 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()
}
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
}
When reviewing Swift concurrency code, check for these issues:
DispatchSemaphore.wait(), DispatchGroup.wait(), or similar blocking calls inside async contextsMainActor.run: Should usually be @MainActor on the function insteadTask { } instead of async let or TaskGroupTask.isCancelled@MainActor (or use @Observable)// 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)
}
}
}
// 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
}
}
// BEFORE: Combine publisher
cancellable = NotificationCenter.default
.publisher(for: .userDidLogin)
.sink { notification in /* ... */ }
// AFTER: AsyncSequence
for await _ in NotificationCenter.default.notifications(named: .userDidLogin) {
// Handle notification
}
| Keyword | Purpose |
|---|---|
async | Function can suspend |
await | Suspension point |
Task { } | Start async work, inherits isolation |
Task.detached { } | Start async work, no inheritance |
@MainActor | Runs on main thread |
actor | Type with isolated mutable state |
nonisolated | Opts out of actor isolation |
nonisolated(nonsending) | Inherits caller's isolation |
@concurrent | Always run on background (Swift 6.2+) |
Sendable | Safe to cross isolation boundaries |
sending | One-way transfer of non-Sendable |
async let | Start parallel work |
TaskGroup | Dynamic parallel work |
For new Xcode 26+ projects, these are enabled by default:
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor
SWIFT_APPROACHABLE_CONCURRENCY = YES
Effects:
nonisolated async functions stay on caller's actor instead of hopping to backgroundFor detailed technical reference, consult the files in references/:
isolation.md - Actors, MainActor, isolation domains, inheritancesendable.md - Sendable protocol, non-Sendable patterns, isolated parameterscommon-mistakes.md - Detailed examples of what to avoidAttribution: This skill is adapted from jamesrochabrun/skills.
npx claudepluginhub astralform-ai/astralform-plugins --plugin ios-developerProvides 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.