---
title: "Concurrency Patterns"
description: "Modern Swift concurrency for responsive, safe macOS apps."
type: skill
canonical_url: https://claudary.paisolsolutions.com/skills/concurrency-patterns
source: "Claudary"
difficulty: intermediate
author: "Claude Code Knowledge Pack"
date: 2026-07-10T11:18:44.732Z
license: CC-BY-4.0
attribution: "Concurrency Patterns — Claudary (https://claudary.paisolsolutions.com/skills/concurrency-patterns)"
---

# Concurrency Patterns
Modern Swift concurrency for responsive, safe macOS apps.

## Overview

# Concurrency Patterns

Modern Swift concurrency for responsive, safe macOS apps.

<async_await_basics>
<simple_async>
```swift
// Basic async function
func fetchData() async throws -> [Item] {
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode([Item].self, from: data)
}

// Call from view
struct ContentView: View {
    @State private var items: [Item] = []

    var body: some View {
        List(items) { item in
            Text(item.name)
        }
        .task {
            do {
                items = try await fetchData()
            } catch {
                // Handle error
            }
        }
    }
}
```
</simple_async>

<task_modifier>
```swift
struct ItemListView: View {
    @State private var items: [Item] = []
    let category: Category

    var body: some View {
        List(items) { item in
            Text(item.name)
        }
        // .task runs when view appears, cancels when disappears
        .task {
            await loadItems()
        }
        // .task(id:) re-runs when id changes
        .task(id: category) {
            await loadItems(for: category)
        }
    }

    func loadItems(for category: Category? = nil) async {
        // Automatically cancelled if view disappears
        items = await dataService.fetchItems(category: category)
    }
}
```
</task_modifier>
</async_await_basics>

<actors>
<basic_actor>
```swift
// Actor for thread-safe state
actor DataCache {
    private var cache: [String: Data] = [:]

    func get(_ key: String) -> Data? {
        cache[key]
    }

    func set(_ key: String, data: Data) {
        cache[key] = data
    }

    func clear() {
        cache.removeAll()
    }
}

// Usage (must await)
let cache = DataCache()
await cache.set("key", data: data)
let cached = await cache.get("key")
```
</basic_actor>

<service_actor>
```swift
actor NetworkService {
    private let session: URLSession
    private var pendingRequests: [URL: Task<Data, Error>] = [:]

    init(session: URLSession = .shared) {
        self.session = session
    }

    func fetch(_ url: URL) async throws -> Data {
        // Deduplicate concurrent requests for same URL
        if let existing = pendingRequests[url] {
            return try await existing.value
        }

        let task = Task {
            let (data, _) = try await session.data(from: url)
            return data
        }

        pendingRequests[url] = task

        defer {
            pendingRequests[url] = nil
        }

        return try await task.value
    }
}
```
</service_actor>

<nonisolated>
```swift
actor ImageProcessor {
    private var processedCount = 0

    // Synchronous access for non-isolated properties
    nonisolated let maxConcurrent = 4

    // Computed property that doesn't need isolation
    nonisolated var identifier: String {
        "ImageProcessor-\\(ObjectIdentifier(self))"
    }

    func process(_ image: NSImage) async -> NSImage {
        processedCount += 1
        // Process image...
        return processedImage
    }

    func getCount() -> Int {
        processedCount
    }
}
```
</nonisolated>
</actors>

<main_actor>
<ui_updates>
```swift
// Mark entire class as @MainActor
@MainActor
@Observable
class AppState {
    var items: [Item] = []
    var isLoading = false
    var error: AppError?

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

        do {
            // This call might be on background, result delivered on main
            items = try await dataService.fetchAll()
        } catch {
            self.error = .loadFailed(error)
        }
    }
}

// Or mark specific functions
class DataProcessor {
    @MainActor
    func updateUI(with result: ProcessResult) {
        // Safe to update UI here
    }

    func processInBackground() async -> ProcessResult {
        // Heavy work here
        let result = await heavyComputation()

        // Update UI on main actor
        await updateUI(with: result)

        return result
    }
}
```
</ui_updates>

<main_actor_dispatch>
```swift
// From async context
await MainActor.run {
    self.items = newItems
}

// Assume main actor (when you know you're on main)
MainActor.assumeIsolated {
    self.tableView.reloadData()
}

// Task on main actor
Task { @MainActor in
    self.progress = 0.5
}
```
</main_actor_dispatch>
</main_actor>

<structured_concurrency>
<task_groups>
```swift
// Parallel execution with results
func loadAllCategories() async throws -> [Category: [Item]] {
    let categories = try await fetchCategories()

    return try await withThrowingTaskGroup(of: (Category, [Item]).self) { group in
        for category in categories {
            group.addTask {
                let items = try await self.fetchItems(for: category)
                return (category, items)
            }
        }

        var results: [Category: [Item]] = [:]
        for try await (category, items) in group {
            results[category] = items
        }
        return results
    }
}
```
</task_groups>

<limited_concurrency>
```swift
// Process with limited parallelism
func processImages(_ urls: [URL], maxConcurrent: Int = 4) async throws -> [ProcessedImage] {
    var results: [ProcessedImage] = []

    try await withThrowingTaskGroup(of: ProcessedImage.self) { group in
        var iterator = urls.makeIterator()

        // Start initial batch
        for _ in 0..<min(maxConcurrent, urls.count) {
            if let url = iterator.next() {
                group.addTask {
                    try await self.processImage(at: url)
                }
            }
        }

        // As each completes, add another
        for try await result in group {
            results.append(result)

            if let url = iterator.next() {
                group.addTask {
                    try await self.processImage(at: url)
                }
            }
        }
    }

    return results
}
```
</limited_concurrency>

<async_let>
```swift
// Concurrent bindings
func loadDashboard() async throws -> Dashboard {
    async let user = fetchUser()
    async let projects = fetchProjects()
    async let notifications = fetchNotifications()

    // All three run concurrently, await results together
    return try await Dashboard(
        user: user,
        projects: projects,
        notifications: notifications
    )
}
```
</async_let>
</structured_concurrency>

<async_sequences>
<for_await>
```swift
// Iterate async sequence
func monitorChanges() async {
    for await change in fileMonitor.changes {
        await processChange(change)
    }
}

// With notifications
func observeNotifications() async {
    let notifications = NotificationCenter.default.notifications(named: .dataChanged)

    for await notification in notifications {
        guard !Task.isCancelled else { break }
        await handleNotification(notification)
    }
}
```
</for_await>

<custom_async_sequence>
```swift
struct CountdownSequence: AsyncSequence {
    typealias Element = Int
    let start: Int

    struct AsyncIterator: AsyncIteratorProtocol {
        var current: Int

        mutating func next() async -> Int? {
            guard current > 0 else { return nil }
            try? await Task.sleep(for: .seconds(1))
            defer { current -= 1 }
            return current
        }
    }

    func makeAsyncIterator() -> AsyncIterator {
        AsyncIterator(current: start)
    }
}

// Usage
for await count in CountdownSequence(start: 10) {
    print(count)
}
```
</custom_async_sequence>

<async_stream>
```swift
// Bridge callback-based API
func fileChanges(at path: String) -> AsyncStream<FileChange> {
    AsyncStream { continuation in
        let monitor = FileMonitor(path: path) { change in
            continuation.yield(change)
        }

        monitor.start()

        continuation.onTermination = { _ in
            monitor.stop()
        }
    }
}

// Throwing version
func networkEvents() -> AsyncThrowingStream<NetworkEvent, Error> {
    AsyncThrowingStream { continuation in
        let connection = NetworkConnection()

        connection.onEvent = { event in
            continuation.yield(event)
        }

        connection.onError = { error in
            continuation.finish(throwing: error)
        }

        connection.onComplete = {
            continuation.finish()
        }

        connection.start()

        continuation.onTermination = { _ in
            connection.cancel()
        }
    }
}
```
</async_stream>
</async_sequences>

<cancellation>
<checking_cancellation>
```swift
func processLargeDataset(_ items: [Item]) async throws -> [Result] {
    var results: [Result] = []

    for item in items {
        // Check for cancellation
        try Task.checkCancellation()

        // Or check without throwing
        if Task.isCancelled {
            break
        }

        let result = await process(item)
        results.append(result)
    }

    return results
}
```
</checking_cancellation>

<cancellation_handlers>
```swift
func downloadFile(_ url: URL) async throws -> Data {
    let task = URLSession.shared.dataTask(with: url)

    return try await withTaskCancellationHandler {
        try await withCheckedThrowingContinuation { continuation in
            task.completionHandler = { data, _, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else if let data = data {
                    continuation.resume(returning: data)
                }
            }
            task.resume()
        }
    } onCancel: {
        task.cancel()
    }
}
```
</cancellation_handlers>

<task_cancellation>
```swift
class ViewModel {
    private var loadTask: Task<Void, Never>?

    func load() {
        // Cancel previous load
        loadTask?.cancel()

        loadTask = Task {
            await performLoad()
        }
    }

    func cancel() {
        loadTask?.cancel()
        loadTask = nil
    }

    deinit {
        loadTask?.cancel()
    }
}
```
</task_cancellation>
</cancellation>

<sendable>
<sendable_types>
```swift
// Value types are Sendable by default if all properties are Sendable
struct Item: Sendable {
    let id: UUID
    let name: String
    let count: Int
}

// Classes must be explicitly Sendable
final class ImmutableConfig: Sendable {
    let apiKey: String
    let baseURL: URL

    init(apiKey: String, baseURL: URL) {
        self.apiKey = apiKey
        self.baseURL = baseURL
    }
}

// Actors are automatically Sendable
actor Counter: Sendable {
    var count = 0
}

// Mark as @unchecked Sendable when you manage thread safety yourself
final class ThreadSafeCache: @unchecked Sendable {
    private let lock = NSLock()
    private var storage: [String: Data] = [:]

    func get(_ key: String) -> Data? {
        lock.lock()
        defer { lock.unlock() }
        return storage[key]
    }
}
```
</sendable_types>

<sending_closures>
```swift
// Closures that cross actor boundaries must be @Sendable
func processInBackground(work: @Sendable @escaping () async -> Void) {
    Task.detached {
        await work()
    }
}

// Capture only Sendable values
let items = items  // Must be Sendable
Task {
    await process(items)
}
```
</sending_closures>
</sendable>

<best_practices>
<do>
- Use `.task` modifier for view-related async work
- Use actors for shared mutable state
- Mark UI-updating code with `@MainActor`
- Check `Task.isCancelled` in long operations
- Use structured concurrency (task groups, async let) over unstructured
- Cancel tasks when no longer needed
</do>

<avoid>
- Creating detached tasks unnecessarily (loses structured concurrency benefits)
- Blocking actors with synchronous work
- Ignoring cancellation in long-running operations
- Passing non-Sendable types across actor boundaries
- Using `DispatchQueue` when async/await works
</avoid>
</best_practices>

---

Source: [Claudary](https://claudary.paisolsolutions.com/skills/concurrency-patterns) · https://claudary.paisolsolutions.com
