Skip to main content

swift-concurrency-developer

Expert guidance on Swift concurrency using the Office Building mental model. Use when working with actors, isolation, Sendable, TaskGroups, or fixing concurrency warnings and data race issues.

Source facts

Repository
anyproto/anytype-swift
Last source activity
May 7, 2026 at 08:50
Detected SKILL.md language
English
Stars
520
Forks
77

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
21 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
swift-concurrency-developer
description
Expert guidance on Swift concurrency using the Office Building mental model. Use when working with actors, isolation, Sendable, TaskGroups, or fixing concurrency warnings and data race issues.
# Swift Concurrency Developer (Smart Router) ## Purpose Expert guidance on Swift's concurrency system using the "Office Building" mental model from [Fucking Approachable Swift Concurrency](https://fuckingapproachableswiftconcurrency.com), combined with comprehensive reference material from [Swift Concurrency Course](https://www.swiftconcurrencycourse.com). ## When Auto-Activated - Working with actors, isolation, Sendable, TaskGroups - Keywords: `actor`, `isolation`, `Sendable`, `TaskGroup`, `nonisolated`, `async let` - Fixing concurrency warnings or data race issues ## Agent Behavior Contract (Follow These Rules) 1. Analyze the project/package file to find out which Swift language mode (Swift 5.x vs Swift 6) and which Xcode/Swift toolchain is used when advice depends on it. 2. Before proposing fixes, identify the isolation boundary: `@MainActor`, custom actor, actor instance isolation, or nonisolated. 3. Do not recommend `@MainActor` as a blanket fix. Justify why main-actor isolation is correct for the code. 4. Prefer structured concurrency (child tasks, task groups) over unstructured tasks. Use `Task.detached` only with a clear reason. 5. If recommending `@preconcurrency`, `@unchecked Sendable`, or `nonisolated(unsafe)`, require: - a documented safety invariant - a follow-up ticket to remove or migrate it 6. For migration work, optimize for minimal blast radius (small, reviewable changes) and add verification steps. 7. Course references are for deeper learning only. Use them sparingly and only when they clearly help answer the developer's question. ## Project Settings Discovery Always confirm these before interpreting diagnostics or giving migration-sensitive guidance. Do not guess — if any are unknown, ask the developer. | Setting | SwiftPM (`Package.swift`) | Xcode (`.pbxproj`) | |---|---|---| | Language mode | `.swiftLanguageMode(.v6)` per-target inside `swiftSettings` (NOT package-level `swiftLanguageVersions`, which only advertises compatibility) | `SWIFT_VERSION` | | Strict concurrency | `.enableExperimentalFeature("StrictConcurrency=targeted")` | `SWIFT_STRICT_CONCURRENCY` | | Default isolation | `.defaultIsolation(MainActor.self)` | `SWIFT_DEFAULT_ACTOR_ISOLATION` | | Upcoming features | `.enableUpcomingFeature("NonisolatedNonsendingByDefault")` | `SWIFT_UPCOMING_FEATURE_*` | Tools: `Read` on `Package.swift`, `Grep` on `.pbxproj`. ## Core Mental Model: The Office Building Think of your app as an office building where **isolation domains** are private offices with locks: | Concept | Office Analogy | Swift | |---------|----------------|-------| | **MainActor** | Front desk (handles all UI) | `@MainActor` | | **actor** | Department offices (Accounting, Legal) | `actor BankAccount { }` | | **nonisolated** | Hallways (shared space) | `nonisolated func name()` | | **Sendable** | Photocopies (safe to share) | `struct User: Sendable` | | **Non-Sendable** | Original documents (stay in one office) | `class Counter { }` | **Key insight**: You can't barge into someone's office. You knock (`await`) and wait. ## Quick Decision Tree When a developer needs concurrency guidance: 1. **Starting fresh with async code?** - Read `references/async-await-basics.md` for foundational patterns - For parallel operations → `references/tasks.md` (async let, task groups) 2. **Protecting shared mutable state?** - Need to protect class-based state → `references/actors.md` (actors, @MainActor) - Need thread-safe value passing → `references/sendable.md` (Sendable conformance) 3. **Managing async operations?** - Structured async work → `references/tasks.md` (Task, child tasks, cancellation) - Streaming data → `references/async-sequences.md` (AsyncSequence, AsyncStream) 4. **Working with legacy frameworks?** - Core Data integration → `references/core-data.md` - General migration → `references/migration.md` 5. **Performance or debugging issues?** - Slow async code → `references/performance.md` (profiling, suspension points) - Testing concerns → `references/testing.md` (XCTest, Swift Testing) 6. **Understanding threading behavior?** - Read `references/threading.md` for thread/task relationship and isolation 7. **Memory issues with tasks?** - Read `references/memory-management.md` for retain cycle prevention ## Triage-First Playbook (Common Errors -> Next Best Move) - **SwiftLint concurrency-related warnings** - Use `references/linting.md` for rule intent and preferred fixes; avoid dummy awaits as "fixes". - **"Sending value of non-Sendable type ... risks causing data races"** - First: identify where the value crosses an isolation boundary - Then: use `references/sendable.md` and `references/threading.md` - **"Main actor-isolated ... cannot be used from a nonisolated context"** - First: decide if it truly belongs on `@MainActor` - Then: use `references/actors.md` (global actors, `nonisolated`, isolated parameters) - **XCTest async errors like "wait(...) is unavailable from asynchronous contexts"** - Use `references/testing.md` (`await fulfillment(of:)` and Swift Testing patterns) - **Core Data concurrency warnings/errors** - Use `references/core-data.md` (DAO/`NSManagedObjectID`, default isolation conflicts) ## Quick Patterns ### Async/Await ```swift func fetchUser(id: Int) async throws -> User { let (data, _) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode(User.self, from: data) } ``` ### Parallel Work with async let ```swift async let avatar = fetchImage("avatar.jpg") async let banner = fetchImage("banner.jpg") return Profile(avatar: try await avatar, banner: try await banner) ``` ### Tasks ```swift // SwiftUI - cancels when view disappears .task { avatar = await downloadAvatar() } // Manual task (inherits actor context) Task { await saveProfile() } ``` ### Task Entry Isolation (Swift 6.2) Match a `Task`'s entry isolation to its **synchronous prefix** — everything from `{` to the first `await`. Whatever runs in that prefix executes on the inherited actor. - If the prefix needs `@MainActor` (touches UI state, mutates `self.isLoading`, etc.), keep the inherited start. - If the prefix has nothing main-actor-bound, prefer `Task { @concurrent in ... }` and hop back via `MainActor.run { ... }` only for the UI mutation. - A trivial non-main statement (e.g. `print`) followed by main-actor work is **not** a reason to switch to `@concurrent` — the cheap line rides along. - For delayed retries, timers, and backoff: separate the waiting from the UI mutation. The sleep usually belongs off-main even when the final state update belongs on-main. ```swift // ❌ Called from @MainActor; fetchData() is nonisolated, so the task starts on main then hops away // (whether the hop happens depends on fetchData()'s declared isolation — nonisolated/@concurrent hop, @MainActor does not) Task { await fetchData() // nonisolated async } // ✅ Start off the main actor, hop back only for UI work Task { @concurrent in let data = try await fetchData() await MainActor.run { self.items = data } } // ✅ Prefix DOES need main actor — keep inheritance Task { self.isLoading = true // needs @MainActor, before any await await fetchData() self.isLoading = false } ``` ### TaskGroup for Dynamic Parallel Work ```swift try await withThrowingTaskGroup(of: Void.self) { group in group.addTask { avatar = try await downloadAvatar() } group.addTask { bio = try await fetchBio() } try await group.waitForAll() } ``` ### Actors ```swift actor BankAccount { var balance: Double = 0 func deposit(_ amount: Double) { balance += amount } // No await needed - can access directly inside actor nonisolated func bankName() -> String { "Acme Bank" } } await account.deposit(100) // Must await from outside let name = account.bankName() // No await needed ``` ### Sendable Types ```swift // Automatically Sendable - value type struct User: Sendable { let id: Int let name: String } // Thread-safe class with internal synchronization final class ThreadSafeCache: @unchecked Sendable { private let lock = NSLock() private var storage: [String: Data] = [:] } ``` ## Common Mistakes ### 1. Thinking async = background ```swift // WRONG: Still blocks main thread! @MainActor func slowFunction() async { let result = expensiveCalculation() // Synchronous = blocking } // CORRECT: Use detached task for CPU-heavy work Task.detached(priority: .userInitiated) { let result = expensiveCalculation() await MainActor.run { updateUI(result) } } ``` > **Production impact**: Apps get rejected for "became unresponsive." See `references/production-pitfalls.md` section 2. ### 2. Creating too many actors Most things can live on MainActor. Only create actors when you have shared mutable state that can't be on MainActor. ### 3. Using MainActor.run unnecessarily ```swift // WRONG await MainActor.run { self.data = data } // CORRECT - annotate the function @MainActor func loadData() async { self.data = await fetchData() } ``` ### 4. Blocking the cooperative thread pool (violates runtime contract) Never use `DispatchSemaphore`, `DispatchGroup.wait()`, or condition variables in async code. **Why**: These primitives hide dependencies from the runtime. The cooperative thread pool has a contract that threads will always make forward progress. Blocking primitives violate this contract and can cause deadlock. ```swift // ❌ DANGEROUS: Can deadlock the cooperative pool let semaphore = DispatchSemaphore(value: 0) Task { await doWork() semaphore.signal() } semaphore.wait() // Thread blocked, runtime unaware // ✅ Use async/await instead let result = await doWork() ``` **Debug tip**: Set `LIBDISPATCH_COOPERATIVE_POOL_STRICT=1` to catch blocking calls during development. ### 5. Creating unnecessary Tasks ```swift // WRONG - unstructured Task { await fetchUsers() } Task { await fetchPosts() } // CORRECT - structured concurrency async let users = fetchUsers() async let posts = fetchPosts() await (users, posts) ``` ### 6. Making everything Sendable Not everything needs to cross boundaries. Ask if data actually moves between isolation domains. ### 7. Not batching MainActor hops The main thread is separate from the cooperative thread pool. Each hop to/from MainActor requires a full context switch. ```swift // ❌ Multiple context switches for item in items { let processed = await processItem(item) await MainActor.run { displayItem(processed) } // Context switch per item } // ✅ Single context switch let processed = await processAllItems(items) await MainActor.run { for item in processed { displayItem(item) } } ``` ### 8. Async for loops silently losing data Using `try?` or empty `catch {}` in async loops swallows failures. Users lose data with zero indication. Acceptable for fire-and-forget (cache warming, analytics), dangerous for uploads/sync/migration. See `references/production-pitfalls.md` section 1. ### 9. Ignoring Task cancellation in long-running loops `for await` under `.task` modifier is safe (structured concurrency propagates cancellation). But `for await` or `while` loops in stored `Task { }` properties need explicit `Task.isCancelled` checks. See `references/production-pitfalls.md` section 3. ### 10. Manual migration pitfalls (@preconcurrency + DispatchQueue mixing) `@preconcurrency` and `nonisolated(unsafe)` hide real data races. Mixing `DispatchQueue` with `async/await` creates confusing execution contexts. Always document safety invariants and plan removal. See `references/production-pitfalls.md` section 4. ### 11. Task inside onAppear instead of .task modifier `Task { }` in `.onAppear` is unstructured: not cancelled on disappear, fires on every re-appear. Use `.task { }` for async work tied to view lifecycle, `.onAppear` for sync-only setup. See `references/production-pitfalls.md` section 5. ## Quick Reference | Keyword | Purpose | |---------|---------| | `async` | Function can pause | | `await` | Pause here until done | | `Task { }` | Start async work, inherits context | | `Task.detached { }` | Start async work, no context | | `@MainActor` | Runs on main thread | | `actor` | Type with isolated mutable state | | `nonisolated` | Opts out of actor isolation | | `Sendable` | Safe to pass between isolation domains | | `@unchecked Sendable` | Trust me, it's thread-safe | | `async let` | Start parallel work | | `TaskGroup` | Dynamic parallel work | ## When the Compiler Complains Trace the isolation: Where did it come from? Where is code trying to run? What data crosses a boundary? The answer is usually obvious once you ask the right question. ## Reference Files Load these files as needed for specific topics: ### Foundational Concepts - **`async-await-basics.md`** - async/await syntax, execution order, async let, URLSession patterns - **`tasks.md`** - Task lifecycle, cancellation, priorities, task groups, structured vs unstructured - **`threading.md`** - Thread/task relationship, suspension points, isolation domains, nonisolated - **`glossary.md`** - Quick definitions of core concurrency terms ### Isolation & Safety - **`actors.md`** - Actor isolation, @MainActor, global actors, reentrancy, custom executors, Mutex - **`sendable.md`** - Sendable conformance, value/reference types, @unchecked, region isolation - **`memory-management.md`** - Retain cycles in tasks, memory safety patterns ### Advanced Patterns - **`async-sequences.md`** - AsyncSequence, AsyncStream, when to use vs regular async methods - **`async-algorithms.md`** - Swift Async Algorithms package, combining sequences - **`task-local-values.md`** - Task-local context propagation, tracing, logging patterns - **`core-data.md`** - NSManagedObject sendability, custom executors, isolation conflicts ### Quality & Migration - **`performance.md`** - Profiling with Instruments, reducing suspension points, execution strategies - **`testing.md`** - XCTest async patterns, Swift Testing, concurrency testing utilities - **`migration.md`** - Swift 6 migration strategy, closure-to-async conversion, @preconcurrency - **`linting.md`** - Concurrency-focused lint rules and SwiftLint async_without_await - **`production-pitfalls.md`** - Silent data loss, cancellation gaps, migration bridges, .task vs onAppear ### Project-Specific References (Anytype) - **`approachable-concurrency.md`** - Approachable concurrency quick guide - **`swift-6-2-concurrency.md`** - Swift 6.2 concurrency updates (future reference)
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub