smithery.ai

swift-concurrency

Swift concurrency expert for async programming. Use when working with async/await, actors, Task, TaskGroup, MainActor, Sendable, continuations, or managing concurrent operations.

First seen Mar 19, 2026

Installation

$ npx skills add https://smithery.ai

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 11,066 B
  • docs SUMMARY.md 203 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 7 installs

SKILL.md

Swift Concurrency

Expert guidance for modern Swift concurrency with async/await and actors.

Async/Await Basics

Async Functions

// Define async function
func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// Call async function
func loadUser() async {
    do {
        let user = try await fetchUser(id: 1)
        print(user.name)
    } catch {
        print("Error: \(error)")
    }
}

Async Properties

struct ImageLoader {
    let url: URL

    var image: UIImage {
        get async throws {
            let (data, _) = try await URLSession.shared.data(from: url)
            guard let image = UIImage(data: data) else {
                throw ImageError.invalidData
            }
            return image
        }
    }
}

// Usage
let loader = ImageLoader(url: imageURL)
let image = try await loader.image

Async Sequences

// Iterate over async sequence
for try await line in url.lines {
    print(line)
}

// With AsyncStream
func notifications() -> AsyncStream<Notification> {
    AsyncStream { continuation in
        let observer = NotificationCenter.default.addObserver(
            forName: .myNotification,
            object: nil,
            queue: nil
        ) { notification in
            continuation.yield(notification)
        }

        continuation.onTermination = { _ in
            NotificationCenter.default.removeObserver(observer)
        }
    }
}

// Usage
for await notification in notifications() {
    print("Received: \(notification)")
}

Task

Basic Task

// Fire and forget
Task {
    await performWork()
}

// With priority
Task(priority: .high) {
    await urgentWork()
}

// Detached task (no context inheritance)
Task.detached {
    await backgroundWork()
}

Task Cancellation

class DataLoader {
    private var loadTask: Task<Void, Never>?

    func startLoading() {
        loadTask = Task {
            do {
                // Check cancellation periodically
                try Task.checkCancellation()

                let data = try await fetchData()

                // Or use isCancelled
                guard !Task.isCancelled else { return }

                await processData(data)
            } catch is CancellationError {
                print("Task was cancelled")
            } catch {
                print("Error: \(error)")
            }
        }
    }

    func cancelLoading() {
        loadTask?.cancel()
    }
}

Task Value

let task = Task {
    try await fetchUser(id: 1)
}

// Later, get result
let user = try await task.value

TaskGroup

Parallel Execution

func fetchAllUsers(ids: [Int]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask {
                try await fetchUser(id: id)
            }
        }

        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

Limiting Concurrency

func processImages(_ urls: [URL], maxConcurrent: Int = 4) async throws -> [UIImage] {
    try await withThrowingTaskGroup(of: UIImage.self) { group in
        var images: [UIImage] = []
        var iterator = urls.makeIterator()

        // Start initial batch
        for _ in 0..<maxConcurrent {
            if let url = iterator.next() {
                group.addTask { try await downloadImage(url) }
            }
        }

        // Process and add more
        for try await image in group {
            images.append(image)
            if let url = iterator.next() {
                group.addTask { try await downloadImage(url) }
            }
        }

        return images
    }
}

First Result Wins

func fetchFromFastestServer() async throws -> Data {
    try await withThrowingTaskGroup(of: Data.self) { group in
        let servers = ["server1.com", "server2.com", "server3.com"]

        for server in servers {
            group.addTask {
                try await fetch(from: server)
            }
        }

        // Return first successful result
        guard let data = try await group.next() else {
            throw NetworkError.noResponse
        }

        // Cancel remaining tasks
        group.cancelAll()

        return data
    }
}

Actors

Basic Actor

actor BankAccount {
    private var balance: Double = 0

    func deposit(_ amount: Double) {
        balance += amount
    }

    func withdraw(_ amount: Double) throws -> Double {
        guard balance >= amount else {
            throw BankError.insufficientFunds
        }
        balance -= amount
        return amount
    }

    func getBalance() -> Double {
        balance
    }
}

// Usage (must await)
let account = BankAccount()
await account.deposit(100)
let balance = await account.getBalance()

Nonisolated

actor DataStore {
    private var items: [Item] = []

    // Synchronous property access
    nonisolated let id = UUID()

    // Computed nonisolated (only from immutable data)
    nonisolated var description: String {
        "DataStore \(id)"
    }

    func addItem(_ item: Item) {
        items.append(item)
    }
}

Actor Reentrancy

actor ImageCache {
    private var cache: [URL: UIImage] = [:]
    private var inProgress: [URL: Task<UIImage, Error>] = [:]

    func image(for url: URL) async throws -> UIImage {
        // Check cache first
        if let cached = cache[url] {
            return cached
        }

        // Check if already downloading
        if let task = inProgress[url] {
            return try await task.value
        }

        // Start new download
        let task = Task {
            try await downloadImage(url)
        }

        inProgress[url] = task

        do {
            let image = try await task.value
            cache[url] = image
            inProgress[url] = nil
            return image
        } catch {
            inProgress[url] = nil
            throw error
        }
    }
}

MainActor

MainActor Annotation

@MainActor
class ViewModel: ObservableObject {
    @Published var items: [Item] = []
    @Published var isLoading = false

    func loadItems() async {
        isLoading = true
        defer { isLoading = false }

        do {
            items = try await fetchItems()
        } catch {
            // Handle error
        }
    }
}

MainActor.run

func processInBackground() async {
    let result = await performHeavyWork()

    // Update UI on main thread
    await MainActor.run {
        updateUI(with: result)
    }
}

MainActor in SwiftUI

struct ContentView: View {
    @StateObject private var viewModel = ViewModel()

    var body: some View {
        List(viewModel.items) { item in
            Text(item.name)
        }
        .task {
            // Automatically runs on MainActor context
            await viewModel.loadItems()
        }
    }
}

Sendable

Sendable Types

// Struct with Sendable properties is implicitly Sendable
struct UserData: Sendable {
    let id: Int
    let name: String
}

// Explicitly mark as Sendable
final class Counter: Sendable {
    let count: Int  // Must be immutable for class

    init(count: Int) {
        self.count = count
    }
}

// @unchecked Sendable (use carefully)
final class ThreadSafeCounter: @unchecked Sendable {
    private var lock = NSLock()
    private var _count = 0

    var count: Int {
        lock.lock()
        defer { lock.unlock() }
        return _count
    }

    func increment() {
        lock.lock()
        defer { lock.unlock() }
        _count += 1
    }
}

Sendable Closures

// @Sendable closure
func performAsync(_ work: @Sendable @escaping () async -> Void) {
    Task {
        await work()
    }
}

// Usage
performAsync { @Sendable in
    await processData()
}

Continuations

Bridging Callbacks to Async

func fetchUser() async throws -> User {
    try await withCheckedThrowingContinuation { continuation in
        oldAPIWithCallback { result in
            switch result {
            case .success(let user):
                continuation.resume(returning: user)
            case .failure(let error):
                continuation.resume(throwing: error)
            }
        }
    }
}

Unsafe Continuation (Performance)

func fetchData() async -> Data {
    await withUnsafeContinuation { continuation in
        // Only resume exactly once!
        legacyFetch { data in
            continuation.resume(returning: data)
        }
    }
}

async let

Concurrent Binding

func loadDashboard() async throws -> Dashboard {
    async let user = fetchUser()
    async let posts = fetchPosts()
    async let notifications = fetchNotifications()

    // All three run concurrently
    return try await Dashboard(
        user: user,
        posts: posts,
        notifications: notifications
    )
}

With Cancellation

func loadData() async throws -> (User, [Post]) {
    async let user = fetchUser()
    async let posts = fetchPosts()

    // If either fails, the other is cancelled
    return try await (user, posts)
}

Best Practices

Structured Concurrency

// Good: Task lifetime tied to scope
func loadData() async {
    await withTaskGroup(of: Void.self) { group in
        group.addTask { await loadUsers() }
        group.addTask { await loadPosts() }
    }
    // Both complete before function returns
}

// Avoid: Unstructured tasks that outlive scope
func loadData() {
    Task { await loadUsers() }  // May outlive function
    Task { await loadPosts() }
}

Error Handling

func fetchWithRetry<T>(
    maxAttempts: Int = 3,
    operation: () async throws -> T
) async throws -> T {
    var lastError: Error?

    for attempt in 1...maxAttempts {
        do {
            return try await operation()
        } catch {
            lastError = error
            if attempt < maxAttempts {
                try await Task.sleep(nanoseconds: UInt64(attempt) * 1_000_000_000)
            }
        }
    }

    throw lastError!
}

Apple Documentation