smithery.ai

swift-approachable-concurrency

Expert guidance on Swift 6.2 Approachable Concurrency for iOS 26+.

First seen Apr 23, 2026

Installation

$ npx skills add https://smithery.ai

Summary

  • Expert guidance on Swift 6.2 Approachable Concurrency for iOS 26+.
  • Use when working with async/await, Tasks, actors, MainActor, Sendable, isolation domains, migrating to Swift 6, fixing concurrency compiler errors, or understanding @concurrent.
  • Provides mental models, migration strategies, and safe concurrent Swift patterns.

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 8,311 B
  • docs SUMMARY.md 364 B

History

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

SKILL.md

Swift 6.2 Approachable Concurrency Guide

Expert guidance on Swift's concurrency system for iOS 26+ apps.

Core Mental Model: The Office Building

Think of your app as an office building where isolation domains are private offices with locks:

  • MainActor = Front desk (handles all UI interactions, only one exists)
  • actor types = Department offices (each protects its own data)
  • nonisolated code = Hallways (shared space, no private documents)
  • Sendable types = Photocopies (safe to share between offices)
  • Non-Sendable types = Original documents (must stay in one office)

You can't barge into someone's office. You knock (await) and wait.

The Core Shift: "Stay on the Caller"

Swift 6.0: Non-isolated async functions ran on the global background executor. Swift 6.2: Non-isolated async functions inherit the caller's isolation by default.

// Swift 6.0 - needed manual MainActor.run
func loadData() async {
    let result = await fetchData()
    await MainActor.run { self.items = result }  // Required!
}

// Swift 6.2 - stays on caller
func loadData() async {
    let result = await fetchData()
    self.items = result  // Safe! Inherits MainActor from caller
}

Async/Await

An async function can pause. Use await to suspend until work finishes. For parallel work, use async let:

async let avatar = fetchImage("avatar.jpg")
async let banner = fetchImage("banner.jpg")
return Profile(avatar: try await avatar, banner: try await banner)

Tasks: Managed vs Unmanaged

⚠️ Unmanaged Tasks Are an Anti-Pattern

Tasks created with Task { } or Task.detached { } are unmanaged — you can't cancel them, know when they finish, or access results.

// ❌ BAD - unmanaged
Task { await fetchUsers() }

// ✅ GOOD - SwiftUI managed, auto-cancels
.task { avatar = await downloadAvatar() }

// ✅ GOOD - restarts when ID changes
.task(id: userID) { avatar = await downloadAvatar(for: userID) }

// ✅ GOOD - structured parallel work
async let users = fetchUsers()
async let posts = fetchPosts()
let (u, p) = await (users, posts)

// ✅ GOOD - dynamic parallel work
try await withThrowingTaskGroup(of: Void.self) { group in
    group.addTask { avatar = try await downloadAvatar() }
    group.addTask { bio = try await fetchBio() }
}

When Task { } is acceptable: Button taps where .task(id:) is impractical.

Isolation Domains

Swift asks "who can access this data?" not "which thread?".

MainActor

For UI. Everything UI-related should be here:

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

Actors

Protect their own mutable state with exclusive access:

actor BankAccount {
    var balance: Double = 0
    func deposit(_ amount: Double) { balance += amount }
}
await account.deposit(100)  // Must await from outside

When to create an actor (Matt Massicotte's rule):

  1. You have non-Sendable state
  2. Operations must be atomic
  3. Those operations can't run on an existing actor

If you can't justify all three → use @MainActor instead.

Nonisolated

Opts out of actor isolation. Cannot access actor's protected state:

actor BankAccount {
    nonisolated func bankName() -> String { "Acme Bank" }
}

Isolation Inheritance

With Approachable Concurrency, isolation flows from MainActor through your code:

  • Functions: Inherit caller's isolation unless explicitly marked
  • Closures: Inherit from context where defined
  • Task { }: Inherits actor isolation from creation site
  • Task.detached { }: No inheritance (avoid it)

Build Settings

Setting Swift 5 Mode Swift 6 Mode
Language Version Swift 5 Swift 6
Strict Concurrency Complete (default)
Approachable Concurrency Yes Yes
Default Actor Isolation MainActor MainActor

Swift 5: Concurrency issues are warnings (gradual migration). Swift 6: Concurrency issues are errors (full safety).

For detailed migration steps, see [references/migration-checklist.md](references/migration-checklist.md).

New Keywords (iOS 26 / Swift 6.2)

@concurrent           // Force background execution
func heavyWork() async -> Result { }

sending               // Transfer non-Sendable safely  
func create() async -> sending NonSendableType { }

nonisolated(nonsending)  // Inherit caller isolation (now default)

Task(name: "debug-id")   // Named tasks for debugging

Sendable

Marks types safe to pass across isolation boundaries.

Automatically Sendable: Structs/enums with Sendable properties, Actors, @MainActor types.

// ✅ Sendable - value type
struct User: Sendable { let id: Int; let name: String }

// ❌ Non-Sendable - mutable class
class Counter { var count = 0 }

// ⚠️ Use sparingly - compiler won't verify
final class ThreadSafeCache: @unchecked Sendable { }

Common Patterns

@MainActor
final class PhotoViewModel: ObservableObject {
    @Published var photos: [Photo] = []
    
    func loadPhotos() async {
        let data = await networkService.fetchPhotos()
        let processed = await processPhotos(data)
        photos = processed
    }
}

@concurrent
private func processPhotos(_ data: [Data]) async -> [Photo] {
    data.compactMap { Photo(data: $0) }
}

Common Mistakes

For detailed error messages and fixes, see [references/troubleshooting.md](references/troubleshooting.md).

Mistake Fix
Thinking async = background Use @concurrent for CPU-heavy work
Creating too many actors Use @MainActor unless you need isolated state
Unnecessary MainActor.run Annotate function with @MainActor instead
Blocking cooperative pool Never use DispatchSemaphore.wait() in async code
Unmanaged Task { } Use .task, async let, or TaskGroup
Overusing @unchecked Sendable Use sending or make properly Sendable

Quick Reference

Keyword Purpose
async / await Pause and resume
.task { } SwiftUI managed, auto-cancels
async let Parallel work
TaskGroup Dynamic parallel work
@MainActor Main thread isolation
actor Custom isolation domain
nonisolated Opt out of isolation
Sendable Safe to cross boundaries
@concurrent Force background (Swift 6.2)
sending Transfer non-Sendable (Swift 6.2)

When the Compiler Complains

Trace the isolation:

  1. Where did the isolation come from?
  2. Where is the code trying to run?
  3. What data is crossing a boundary?

Skill References

  • [references/migration-checklist.md](references/migration-checklist.md) - Step-by-step migration guide
  • [references/migration-examples.md](references/migration-examples.md) - Before/after code examples
  • [references/troubleshooting.md](references/troubleshooting.md) - Common errors and fixes

External References

Swift Evolution Proposals