Install
$ agentstack add skill-rusel95-ios-agent-skills-swift-concurrency ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Swift Concurrency
Enterprise-grade skill for Swift Concurrency crash prevention, strict concurrency migration, and Swift 6.2 readiness. Opinionated: prescribes structured concurrency over unstructured, actors for shared mutable state, @MainActor for UI isolation, Mutex for synchronous critical sections, compile-time isolation over runtime hops, and withCheckedContinuation over withUnsafeContinuation. Every rule in this skill has broken a real production app.
Concurrency Layers
Application Layer -> Structured concurrency: TaskGroup, async let, .task modifier
Actor Layer -> actor for shared mutable state, @MainActor for UI state
Async/Await Layer -> Cooperative, non-blocking async functions on the cooperative pool
Sendable Layer -> Compile-time data-race safety at isolation boundary crossings
Compiler Layer -> SWIFT_STRICT_CONCURRENCY=complete, Swift 6 language mode, TSan
Quick Decision Trees
"What isolation does this type need?"
Does this type own mutable state shared across concurrency domains?
+-- YES -> Does it need to update UI?
| +-- YES -> @MainActor class/struct
| +-- NO -> Is the state complex or involves await points?
| +-- YES -> actor
| +-- NO -> Mutex (Swift 6+, iOS 18+) or NSLock wrapper
+-- NO -> Is it a value type with only Sendable stored properties?
+-- YES -> Implicitly Sendable (internal types only; public needs explicit conformance)
+-- NO -> Can it be redesigned as a value type?
+-- YES -> Refactor to struct/enum
+-- NO -> Keep non-Sendable, contain within single isolation domain
"async let vs TaskGroup vs Task {}?"
How many concurrent operations?
+-- Known at compile time (2-5) -> async let (simplest, heterogeneous return types OK)
+-- Dynamic count (N items) -> TaskGroup
| +-- Need results?
| | +-- YES -> withThrowingTaskGroup (iterate with for await)
| | +-- NO -> withDiscardingTaskGroup (no result accumulation leak)
| +-- Unbounded input? -> Throttle: limit addTask to activeProcessorCount
+-- Fire-and-forget from sync context?
+-- Need caller's isolation -> Task { } (inherits actor context)
+-- Need background, no isolation -> Task.detached { }
WARNING: detached strips priority, task-locals, cancellation propagation. Rarely correct.
"Is this safe to call from an async context?"
Does the API block the current thread?
+-- YES (semaphore.wait, group.wait, Thread.sleep, sync file I/O, NSLock in long section)
| -> NEVER in async context. Deadlocks cooperative pool (capped at CPU core count).
| -> Wrap in DispatchQueue + withCheckedContinuation to move off cooperative pool.
+-- NO -> Is the work CPU-bound and takes >1ms?
+-- YES -> Use @concurrent func (Swift 6.2+) or nonisolated func on dedicated queue
+-- NO -> Safe in async context
Workflows
Workflow: Audit Existing Codebase
When: First encounter with a codebase, or preparing for Swift 6 migration.
- Check
SWIFT_STRICT_CONCURRENCYsetting (references/compiler-flags-ci.md) - Scan for crash patterns: continuation misuse, cooperative pool blocking, TaskGroup without throttling (
references/crash-patterns.md) - Scan for
@unchecked Sendableusage (references/sendable-transfer.md) - Check actor isolation: reentrancy bugs, deinit access, split isolation (
references/actor-isolation.md) - Check AsyncStream usage: missing
finish(), infinite sequences, no backpressure (references/asyncstream-memory.md) - For security-sensitive code: token refresh, TOCTOU, Keychain (
references/security-concurrency.md) - Create
refactoring/directory with severity-ranked findings (references/refactoring-workflow.md) - Execute fixes: 🔴 crash → 🟡 hang → 🟠 data race → 🟢 best practice
Workflow: Migrate Module to Strict Concurrency
When: Enabling SWIFT_STRICT_CONCURRENCY=complete or Swift 6 mode.
The migration is a THREE-STEP progression, not a binary flip. Do not jump directly to complete or to Swift 6 language mode — you will drown in errors. The canonical progression is:
| Step | Setting | What it does | |:----:|---------|--------------| | 1. Targeted | SWIFT_STRICT_CONCURRENCY=targeted | Surfaces warnings only in code explicitly marked Sendable or in @preconcurrency boundaries. Low-noise entry point. Ship this first, fix the surfaced warnings, then advance. | | 2. Complete | SWIFT_STRICT_CONCURRENCY=complete | Enables all concurrency checks as warnings. Work through each module until the warning count is zero. Still compiles and ships while you migrate. | | 3. Swift 6 language mode | SWIFT_VERSION=6.0 | Promotes all concurrency checks from warnings to errors. Only switch here AFTER the module is clean under complete. This is the final gate. |
Most teams spend weeks in step 2 before advancing to step 3. Enabling step 3 prematurely produces hundreds of errors with no actionable incremental path.
- Start with leaf modules (no internal dependencies) (
references/migration-strategy.md) - Enable targeted on the leaf first to surface obvious Sendable gaps
- Audit third-party SDKs for Sendable conformance; wrap blockers in actors (
references/migration-strategy.md) - Fix global mutable state:
static var→ actor, Mutex, orlet(references/actor-isolation.md) - Advance to complete on the leaf, fix each warning module by module
- Annotate Sendable on public types; use
sendingwhere applicable (references/sendable-transfer.md) - Check Swift 6.2 caller-side isolation changes (
references/swift-6-2-changes.md) - Only after complete is warning-free on the leaf, advance that leaf to Swift 6 language mode
- Enable per-target in SPM (
references/compiler-flags-ci.md) - Run TSan +
LIBDISPATCH_COOPERATIVE_POOL_STRICT=1(references/testing-debugging.md) - One PR per module, bottom-up order
Workflow: Create New Concurrent Feature
When: Building a new feature from scratch with Swift Concurrency.
- Choose isolation model using Decision Tree 1 (actor vs @MainActor vs Mutex)
- Choose concurrency structure using Decision Tree 2 (async let vs TaskGroup)
- Implement with Sendable-clean types (
references/sendable-transfer.md) - Add cancellation handling (
references/advanced-patterns.md) - Use Clock injection for testability (
references/testing-debugging.md) - Write deterministic tests with
withMainSerialExecutor(references/testing-debugging.md)
Workflow: Fix Production Crash
When: Crash report points to continuation, actor, AsyncStream, or TaskGroup.
- Classify crash type (
references/crash-patterns.md) - Check if release-only (optimized builds strip cooperative pool assertions) -- Rule 9
- For reentrancy crashes: check state assumptions after await (
references/actor-isolation.md) - For security crashes: token refresh, TOCTOU patterns (
references/security-concurrency.md) - Add targeted test reproducing the crash (
references/testing-debugging.md) - Verify fix with TSan +
LIBDISPATCH_COOPERATIVE_POOL_STRICT=1
Code Generation Rules
Whether reviewing, generating, or refactoring concurrent code, every output must be data-race-free, deadlock-free, and production-ready under Swift 6 strict concurrency. ALWAYS:
- Never block the cooperative thread pool -- no
semaphore.wait(),group.wait(),Thread.sleep(), synchronous file I/O in any async context - Resume every continuation exactly once on every code path -- use
withCheckedThrowingContinuation - Re-check actor state after every
await-- actors are reentrant at suspension points - Use
withDiscardingTaskGroupfor fire-and-forget child tasks -- prevents result accumulation memory leaks - AsyncStream must use
onTerminationfor EVERY external resource cleanup — NotificationCenter observers, delegate assignments, timers, KVO, Combine subscribers, or any observer/callback registration made inside the stream's setup closure.deinitis not sufficient because an AsyncStream can outlive or be dropped independently of its owner;onTerminationfires when the stream is finished or its task is cancelled, which is exactly when cleanup must run. Always pair everyaddObserver/delegate = self/registration call inside the stream setup with a symmetric removal inonTermination. Also callcontinuation.finish()from insideonTerminationwhen your stream is an infinite observer pattern - Mark public types with explicit
Sendableconformance -- no automatic inference across module boundaries - Limit TaskGroup child task count for unbounded work -- throttle to
ProcessInfo.processInfo.activeProcessorCount - Use
@MainActorannotation for UI isolation, notMainActor.run {} - Handle
CancellationErrorsilently -- never surface "cancelled" to users - Inject
Clockprotocol for time-dependent code -- never hardcodeTask.sleep - Check Swift 6.2 caller-side isolation --
nonisolated asyncnow inherits caller's actor; use@concurrentfor explicit background - Before generating concurrent code, output a brief `` analyzing isolation domains, Sendable conformance, and potential reentrancy
Fallback Strategies & Loop Breakers
When fixing concurrency issues, you may encounter cascading compiler errors. If you fail to fix the same issue twice, break the loop:
- Sendable spiral: Conforming a type to Sendable cascades into 20+ errors across files. Temporarily use
@preconcurrency importfor the offending module and log a migration task inrefactoring/discovered.md. Why:@preconcurrencysuppresses Sendable checking at module boundaries while preserving local safety — it's the sanctioned escape hatch, unlike@unchecked Sendablewhich bypasses all checking. - Actor isolation cascade: Adding
@MainActorto a class cascades into dozens of async call-site errors. Start by marking individual methods@MainActorinstead of the whole class. Why: class-level isolation propagates to all methods, forcing every call site to be async — method-level isolation limits the blast radius. - Third-party SDK blocker: A third-party type is not Sendable and cannot be made so. Wrap it in a dedicated actor that owns the instance and mediates all access. Why: the actor serializes all access, making the non-Sendable type safe without needing to modify the SDK.
- TaskGroup OOM: TaskGroup spawns thousands of child tasks and OOMs. Add a semaphore-based throttle: acquire before
addTask, release inside the task. Limit toProcessInfo.processInfo.activeProcessorCount * 2. Why: each child task retains its result until the group iterates — unbounded tasks accumulate results in memory.
Confidence Checks
Before finalizing generated or refactored concurrent code, verify ALL:
[] No cooperative pool blocking -- no semaphore.wait, Thread.sleep, sync I/O in any async function
[] No continuation leaks -- every withChecked*Continuation resumes on every path (success, failure, cancellation)
[] No unchecked Sendable -- every @unchecked Sendable has documented synchronization (Mutex, NSLock, or actor)
[] Actor reentrancy -- state re-checked after every await inside actors
[] AsyncStream cleanup -- finish() called in onTermination, no infinite sequence without cancellation
[] TaskGroup bounded -- child task count limited for unbounded input
[] MainActor correct -- UI state @MainActor-isolated, no MainActor.run anti-pattern
[] Swift 6.2 ready -- nonisolated async caller-isolation understood, @concurrent used where needed
[] Cancellation handled -- withTaskCancellationHandler for long-running ops, CancellationError caught silently
[] Tests deterministic -- Clock injected, withMainSerialExecutor used, no flaky timing dependencies
[] Compiler flags -- SWIFT_STRICT_CONCURRENCY=complete set, TSan enabled in CI
References
> Start here for most tasks: crash-patterns.md, actor-isolation.md, sendable-transfer.md. Then consult the specific reference based on your workflow.
| Reference | When to Read | |-----------|-------------| | references/rules.md | Do's and Don'ts quick reference: priority rules and critical anti-patterns | | references/crash-patterns.md | Production crash patterns: continuation misuse, cooperative pool deadlocks, TaskGroup OOM, watchdog kills, release-only crashes | | references/actor-isolation.md | Task.init inherits isolation, compile-time isolation, nonisolated deinit, isolated deinit (Swift 6.1+), split isolation, assumeIsolated, actors as advanced tools | | references/sendable-transfer.md | sending keyword, region-based isolation, @unchecked Sendable risks, public type inference, @preconcurrency scope | | references/asyncstream-memory.md | Infinite sequence leaks, continuation.finish(), backpressure policies, withDiscardingTaskGroup, Task.detached stripping | | references/swift-6-2-changes.md | Approachable Concurrency: caller isolation, @concurrent, MainActor default, @preconcurrency runtime crashes | | references/migration-strategy.md | Bottom-up module migration, third-party SDK blockers, global singleton conversion, actor hopping overhead | | references/security-concurrency.md | Token refresh serialization, TOCTOU at await, Keychain serialization, sensitive data, actor double-spend | | references/compiler-flags-ci.md | SWIFTSTRICTCONCURRENCY, SPM per-target flags, Swift 6.2 feature flags, SwiftLint rules, CI pipeline | | references/advanced-patterns.md | Mutex vs actors, async let vs TaskGroup, isolated parameters, withTaskCancellationHandler, .task modifier, task-locals | | references/diagnostics-fix-mapping.md | Compiler error → fix mapping: "Sending risks data races", "non-sendable capture", "static property not safe", isolation errors | | references/cancellation-patterns.md | Cooperative cancellation, withTaskCancellationHandler, CancellationError handling, timeout patterns, SwiftUI .task | | references/memory-retain-cycles.md | Task retain cycles, weak self patterns, async sequence retention, isolated deinit, testing for leaks | | references/core-data-concurrency.md | NSManagedObject not Sendable, DAO pattern, NSManagedObjectID, actor-isolated context, @MainActor conflicts | | references/testing-async.md | Swift Testing async, confirmation(), Clock injection, withMainSerialExecutor, TSan, deterministic tests | | references/testing-debugging.md | withMainSerialExecutor, Clock injection, TSan, Instruments, deterministic tests, timeouts | | references/refactoring-workflow.md | refactoring/ directory protocol, per-feature plans, severity ordering, PR sizing, verification checklist |
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: rusel95
- Source: rusel95/ios-agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.