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.
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 laxrajpurohit/swift-skills-pro --plugin swift-concurrency-pro