All skills
dpearson2699 avatar

/swift-concurrency

@bac5344

Resolve Swift concurrency compiler errors, adopt approachable concurrency (SE-0466), and write data-race-safe async code. Use when fixing Sendable conformance errors, actor isolation warnings, or strict concurrency diagnostics; when adopting default MainActor isolation, @concurrent, nonisolated(nonsending), or Task.immediate; when designing actor-based architectures, structured concurrency with TaskGroup, or background work offloading; or when migrating from @preconcurrency to full Swift 6 strict concurrency.

Use this Skill: https://skilld.dev/gh/dpearson2699/swift-ios-skills/swift-concurrency

This session only. Nothing lands on disk.

referencesswiftui-concurrency.md

≈1.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

SwiftUI Concurrency Guide

Concurrency patterns and best practices specific to SwiftUI applications.

Contents

MainActor Default in SwiftUI

  • View is @MainActor isolated by default; body and all members inherit this isolation.
  • Swift 6.2 can infer @MainActor for all types in a module via default actor isolation (SE-0466).
  • This default aligns with UIKit/AppKit @MainActor APIs and simplifies UI code.

Where SwiftUI Runs Code Off the Main Thread

SwiftUI may evaluate some view logic on background threads for performance:

  • Shape path generation
  • Layout methods (sizeThatFits, placeSubviews)
  • visualEffect closures
  • onGeometryChange closures

These APIs often require Sendable closures to reflect their off-main-thread runtime semantics.

Sendable Closures and Data-Race Safety

Accessing @MainActor state from a Sendable closure is unsafe and flagged by the compiler.

Fix: Capture value copies in the closure capture list.

// WRONG: Captures @MainActor state directly
.visualEffect { content, proxy in
    content.offset(y: self.offset)  // Error: @MainActor state in Sendable closure
}

// CORRECT: Capture a copy
let currentOffset = offset
// ... use in closure:
.visualEffect { [currentOffset] content, proxy in
    content.offset(y: currentOffset)
}

Avoid sending self into a Sendable closure just to read a single property.

Structuring Async Work

SwiftUI action callbacks are synchronous so UI updates (like loading states) can be immediate.

struct ContentView: View {
    @State private var isLoading = false
    @State private var result: String?

    var body: some View {
        Button("Load") {
            isLoading = true           // Immediate UI update
            Task {
                result = await fetchData()
                isLoading = false
            }
        }
    }
}

Pattern: Use state as the boundary. Async work updates model/state; UI reacts synchronously.

The .task Modifier

Prefer .task over manual Task creation in views:

.task {
    await loadInitialData()
}

Advantages:

  • Automatically cancels on view disappear.
  • Inherits the view's actor isolation (@MainActor).
  • No need to store Task references for cancellation.

Use .task(id:) to restart work when a value changes:

.task(id: selectedItem) {
    details = await fetchDetails(for: selectedItem)
}

@Observable View Models

  • Annotate view models with both @Observable and @MainActor.
  • Use @State to own an @Observable instance (replaces @StateObject).
  • Avoid @ObservedObject / @StateObject / ObservableObject in new code.
@Observable @MainActor
final class ViewModel {
    var items: [Item] = []
    var isLoading = false

    func load() async {
        isLoading = true
        items = await fetchItems()
        isLoading = false
    }
}

struct ItemListView: View {
    @State private var viewModel = ViewModel()

    var body: some View {
        List(viewModel.items) { item in
            Text(item.name)
        }
        .task { await viewModel.load() }
    }
}

Async Observation with Observations (SE-0475)

Use Observations { } for transactional async observation:

.task {
    for await _ in Observations { viewModel.searchText } {
        await viewModel.performSearch()
    }
}

Performance-Driven Concurrency

  • Offload expensive work from the main actor to avoid hitches.
  • Keep time-sensitive UI logic (animations, gesture responses) synchronous.
  • Separate UI code from long-running async work.
@Observable @MainActor
final class ImageProcessor {
    var processedImage: UIImage?

    func process(data: Data) async {
        // Offload heavy work
        let result = await Self.runProcessing(data: data)
        processedImage = result
    }

    @concurrent
    nonisolated static func runProcessing(data: Data) async -> UIImage {
        // Runs on background thread pool
        // ...
    }
}

Common SwiftUI Concurrency Mistakes

  1. Creating Task in body. Use .task modifier instead.
  2. Not cancelling tasks. .task does this automatically; manual Task references must be cancelled in onDisappear.
  3. Blocking MainActor in view updates. Move heavy computation to @concurrent functions.
  4. Using Task.detached in views. Loses actor context. Use Task { } or .task modifier.
  5. Updating state from background. Always update @State / @Observable properties on @MainActor.

Source: SKILL.md on GitHub

2 warnings16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides comprehensive documentation and best practices for Swift Concurrency (Swift 6.2+), including guidance on actor isolation, Sendable safety, and synchronization primitives. No security issues or malicious patterns were identified.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: MEDIUM · 1 issue

  • Runlayer6mo

    5 files scanned · No issues

  • ZeroLeaks5mo

    1 finding · Score: 69/100

Signed by skilld at bac5344. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Steadyupdated 2 months ago

README badge

README badge for dpearson2699/swift-ios-skills/swift-concurrency