Provides guidance on async/await, actors, Sendable, strict concurrency, and migrating completion handlers to Swift 6.
How this skill is triggered — by the user, by Claude, or both
Slash command
/swift-concurrency-pro:swift-concurrency-proThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Write correct, data-race-free concurrent Swift. Target Swift 6 strict concurrency.
Write correct, data-race-free concurrent Swift. Target Swift 6 strict concurrency.
Sendable / data-race / actor-isolation errors.Trigger: /swift-concurrency-pro.
@MainActor. Background work runs off the main actor.actor, never a lock + global.Sendable.❌
func loadUser(completion: @escaping (Result<User, Error>) -> Void) { ... }
✅
func loadUser() async throws -> User { ... }
Wrap legacy callbacks with continuations:
func loadUser() async throws -> User {
try await withCheckedThrowingContinuation { cont in
legacyLoad { result in cont.resume(with: result) }
}
}
Resume a continuation exactly once — never zero, never twice.
❌ Lock around shared dictionary
final class Cache {
private var store: [String: Data] = [:]
private let lock = NSLock()
func set(_ d: Data, _ k: String) { lock.lock(); store[k] = d; lock.unlock() }
}
✅
actor Cache {
private var store: [String: Data] = [:]
func set(_ d: Data, for k: String) { store[k] = d }
func get(_ k: String) -> Data? { store[k] }
}
Access is await cache.set(...). Don't expose var actor state directly across actors.
@MainActor
@Observable
final class FeedModel {
var posts: [Post] = []
func refresh() async {
let fetched = await api.posts() // api hops off main as needed
posts = fetched // back on main, safe
}
}
Don't sprinkle DispatchQueue.main.async — annotate with @MainActor instead.
struct/enum of Sendable members → automatically Sendable.Sendable.actor, or isolate it.❌
class Settings { var theme = "light" } // shared across tasks → data race
✅
actor Settings { var theme = "light" }
// or, if truly immutable:
struct Settings: Sendable { let theme: String }
Use async let / TaskGroup for parallel work; they auto-cancel and propagate errors.
✅
async let a = api.profile()
async let b = api.feed()
let (profile, feed) = try await (a, b)
Avoid unstructured Task { } for work tied to a view's lifetime — use .task so it
cancels on disappear.
DispatchQueue.main.async instead of @MainActor.actor.Sendable type sent across a concurrency boundary.Task {} for view-scoped work (won't cancel).Per issue: file:line, the isolation/Sendable rule violated, before/after fix. End with the highest-risk data races first.
npx claudepluginhub laxrajpurohit/swift-skills-pro --plugin swift-concurrency-proCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.