Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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 stric
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 114% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 85% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 88% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 156% | 0% |
Review, fix, and write concurrent Swift code targeting Swift 6.3+. Gate Swift 6.4 / Xcode 27 beta cleanup APIs behind explicit toolchain and availability checks. Apply actor isolation, Sendable safety, and modern concurrency patterns with minimal behavior changes.
@Observable and ConcurrencyWhen diagnosing a concurrency issue, follow this sequence:
workarounds.
MainActor.Complete / Targeted / Minimal only when auditing Swift 5 migration settings.
@MainActor, custom actor,nonisolated) and whether a default isolation mode is active.
Prefer edits that preserve existing behavior while satisfying data-race safety.
| Situation | Recommended fix | |---|---| | UI-bound type | Annotate the type or relevant members with @MainActor. | | Protocol conformance on MainActor type | Use an isolated conformance: extension Foo: @MainActor Proto. | | Global / static state | Protect with @MainActor or move into an actor. | | Background work needed | Use a @concurrent async function on a nonisolated type. | | Sendable error | Prefer immutable value types. Add Sendable only when correct. | | Cross-isolation callback | Use sending parameters (SE-0430) for finer control. |
@unchecked Sendable or nonisolated(unsafe) was added.remediation. Do not add Thread Sanitizer, broad migration ordering, or architecture advice unless the prompt asks for diagnostics or migration.
Swift 6.2 introduces "approachable concurrency" -- a set of language changes that make concurrent code safer by default while reducing annotation burden. In Xcode, Approachable Concurrency and Default Actor Isolation are separate build settings: use Approachable Concurrency for the bundled upcoming-feature flags, and set Default Actor Isolation to MainActor when you want unannotated code inferred as @MainActor.
With the -default-isolation MainActor compiler flag, SwiftPM .defaultIsolation(MainActor.self), or Xcode's Default Actor Isolation setting set to MainActor, unannotated declarations in the module are inferred as @MainActor unless explicitly opted out.
Effect: Eliminates most data-race safety errors for UI-bound code and global/static state without writing @MainActor everywhere.
swift// With default MainActor isolation enabled, these are implicitly @MainActor: final class StickerLibrary { static let shared = StickerLibrary() // safe -- on MainActor var stickers: [Sticker] = [] } final class StickerModel { let photoProcessor = PhotoProcessor() var selection: [PhotosPickerItem] = [] } // Conformances are also implicitly isolated: extension StickerModel: Exportable { func export() { photoProcessor.exportAsPNG() } }
When to use: Recommended for apps, scripts, and other executable targets where most code is UI-bound. Not recommended for library targets that should remain actor-agnostic.
Nonisolated async functions now stay on the caller's actor by default instead of hopping to the global concurrent executor. This is the nonisolated(nonsending) behavior.
swiftclass PhotoProcessor { func extractSticker(data: Data, with id: String?) async -> Sticker? { // In Swift 6.2+, this runs on the caller's actor (e.g., MainActor) // instead of hopping to a background thread. // ... } } @MainActor final class StickerModel { let photoProcessor = PhotoProcessor() func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? { guard let data = try await item.loadTransferable(type: Data.self) else { return nil } // No data race -- photoProcessor stays on MainActor return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier) } }
Use @concurrent to explicitly request background execution when needed.
@concurrent Attribute@concurrent ensures a function always runs on the concurrent thread pool, freeing the calling actor to run other tasks.
swiftclass PhotoProcessor { var cachedStickers: [String: Sticker] = [:] func extractSticker(data: Data, with id: String) async -> Sticker { if let sticker = cachedStickers[id] { return sticker } let sticker = await Self.extractSubject(from: data) cachedStickers[id] = sticker return sticker } @concurrent static func extractSubject(from data: Data) async -> Sticker { // Expensive image processing -- runs on background thread pool // ... } }
To move a function to a background thread, show both opt-outs together:
nonisolated or the function can be calledfrom a nonisolated context.
@concurrent to the offloaded function. nonisolated alone does notmove CPU-heavy work off the caller's actor.
async if not already asynchronous.await at call sites.swiftnonisolated struct PhotoProcessor { @concurrent func process(data: Data) async -> ProcessedPhoto? { /* ... */ } } // Caller: processedPhotos[item.id] = await PhotoProcessor().process(data: data)
Task.immediate starts executing synchronously on the current actor before any suspension point, rather than being enqueued.
swiftTask.immediate { await handleUserInput() }
Use for latency-sensitive work that should begin without delay. There is also Task.immediateDetached which combines immediate start with detached semantics.
Observations { } provides async observation of @Observable types via AsyncSequence, enabling transactional change tracking.
swiftfor await _ in Observations { model.count } { print("Count changed to \(model.count)") }
A conformance that needs MainActor state is called an isolated conformance. The compiler ensures it is only used in a matching isolation context.
swiftprotocol Exportable { func export() } // Isolated conformance: only usable on MainActor extension StickerModel: @MainActor Exportable { func export() { photoProcessor.exportAsPNG() } } @MainActor struct ImageExporter { var items: [any Exportable] mutating func add(_ item: StickerModel) { items.append(item) // OK -- ImageExporter is on MainActor } }
If ImageExporter were nonisolated, adding a StickerModel would fail: "Main actor-isolated conformance of 'StickerModel' to 'Exportable' cannot be used in nonisolated context."
ContinuousClock and SuspendingClock now expose .epoch (SE-0473), enabling instant comparison and conversion between clock types.
swiftlet continuous = ContinuousClock() let elapsed = continuous.now - continuous.epoch // Duration since system boot
@MainActor for all UI-touching code. No exceptions.Global actors are actor isolation; use @MainActor as the standard pattern for UI-bound shared state.
nonisolated only for methods that access immutable (let) propertiesor are pure computations.
@concurrent to explicitly move work off the caller's actor.nonisolated(unsafe) unless you have proven data-race safety andexhausted safer alternatives. It is an unsafe audit boundary, not a synchronization primitive. For the narrow unsafe-pointer capture pattern in a synchronous parallel loop, follow the proof requirements in bridging and interop. Disjoint per-iteration access can eliminate conflicting concurrent access and the need for synchronization; disjointness is not itself synchronization.
NSLock, DispatchSemaphore) inside actors.Sendable when all storedproperties are Sendable. For diagnostics on mutable reference types, first extract an immutable Sendable value snapshot or DTO instead of sharing the reference.
Sendable.@MainActor classes are implicitly Sendable. Do NOT add redundantSendable conformance.
final with all stored properties let andSendable.
@unchecked Sendable is a last resort. Document why the compiler cannotprove safety.
sending parameters (SE-0430) for finer-grained isolation control.@preconcurrency import only for third-party libraries you cannotmodify. Plan to remove it.
defer blocks in async contexts can contain await in Swift 6.4+ (SE-0493). Use for async cleanup: closing connections, flushing buffers, or releasing resources that require an async call.
The defer body inherits the surrounding isolation and is implicitly awaited at scope exit. It does not suppress cancellation; cleanup that checks Task.isCancelled or Task.checkCancellation() still observes cancellation.
swiftfunc fetchData() async throws -> Data { let connection = try await openConnection() defer { await connection.close() } return try await connection.read() }
Task: Unstructured, inherits caller context.
swiftTask { await doWork() }
Task.detached: No inherited context. Use only when you explicitly need to break isolation inheritance.
Task.immediate: Starts immediately on current actor. Use for latency-sensitive work.
swiftTask.immediate { await handleUserInput() }
async let: Fixed number of concurrent operations.
swiftasync let a = fetchA() async let b = fetchB() let result = try await (a, b)
TaskGroup: Dynamic number of concurrent operations.
swifttry await withThrowingTaskGroup(of: Item.self) { group in for id in ids { group.addTask { try await fetch(id) } } for try await item in group { process(item) } }
Task.isCancelled or calltry Task.checkCancellation() in loops.
.task modifier in SwiftUI -- it handles cancellation on view disappear.withTaskCancellationHandler for cleanup.withTaskCancellationShield only for shortcleanup or rollback that must complete after cancellation. Inside the shield, Task.isCancelled is false and Task.checkCancellation() does not throw; cancellation is observable again after the scope exits.
deinit or onDisappear.Actors are reentrant. State can change across suspension points.
swift// WRONG: State may change during await actor Counter { var count = 0 func increment() async { let current = count await someWork() count = current + 1 // BUG: count may have changed } } // CORRECT: Mutate synchronously, no reentrancy risk actor Counter { var count = 0 func increment() { count += 1 } }
Use AsyncStream to bridge callback/delegate APIs:
swiftlet stream = AsyncStream<Location> { continuation in let delegate = LocationDelegate { location in continuation.yield(location) } continuation.onTermination = { _ in delegate.stop() } delegate.start() }
Use withCheckedContinuation / withCheckedThrowingContinuation for single-value callbacks. Resume exactly once.
@Observable and Concurrency@Observable classes should be @MainActor for view models.@State to own an @Observable instance (replaces @StateObject).Observations { } (SE-0475) for async observation of @Observableproperties as an AsyncSequence.
When actors are not the right fit — synchronous access, performance-critical paths, or bridging C/ObjC — use low-level synchronization primitives:
they give compiler-enforced isolation and structured-concurrency integration, but outside calls are async actor hops, require reentrancy care across await, and do not fit synchronous C callbacks. Use global actors such as @MainActor for UI-bound shared state; never use nonisolated(unsafe) as a synchronization substitute.
Mutex<Value> (iOS 18+, Synchronization module): Preferred lock fornew code. Stores protected state inside the lock. withLock { } pattern.
OSAllocatedUnfairLock (iOS 16+, os module): Use when targetingolder iOS versions. Supports ownership assertions for debugging.
Atomic<Value> (iOS 18+, Synchronization module): Lock-free atomicsfor independent counters and flags. Atomic is Sendable and can be stored in Sendable holder types. Use .relaxed only for standalone metrics; use acquire/release ordering or a lock when coordinating other data.
Key rule: Never put locks inside actors (double synchronization), and never hold a lock across await (blocks a thread through suspension and can starve the cooperative pool or deadlock). See references/synchronization-primitives.md for full API details, code examples, and a decision guide for choosing locks vs actors. Mutex.withLock and OSAllocatedUnfairLock.withLock use synchronous closures; that API shape is what keeps critical sections non-suspending. Gate Mutex and Atomic with runtime if #available(iOS 18, *), never #if swift(...) or platform compile-time checks. For NSLock, correct only the false Sendable premise and avoid explaining conformance mechanics. If a legacy lock wrapper truly needs @unchecked Sendable, name the invariant: all mutable state is private, all access uses one lock, no mutable references escape, and no lock is held across await.
@MainActor freezes UI.Move to a @concurrent function.
do not need @MainActor. Only UI-touching code does.
plain struct or function.
Sendable struct, not an actor.and cancellation propagation.
Task references and cancel them, oruse the .task view modifier.
[weak self] when capturing self inlong-lived stored tasks.
DispatchSemaphore.wait() can block a cooperative executor thread and can deadlock when the signaller needs the blocked executor. Use an async primitive or structured concurrency.@MainActor and nonisolated properties in onetype. Isolate the entire type consistently.
@MainActor funcover await MainActor.run { }.
actors, and task groups. Keep dispatch queues where an API requires a queue, for custom executors, or during bounded legacy interop. A measured, synchronous CPU-bound parallel-for can still use DispatchQueue.concurrentPerform; a task group is an async design alternative, not a drop-in replacement for a synchronous API. Follow the capture and memory-safety audit in bridging and interop. In every review of this diagnostic, explicitly connect its @Sendable operation to both non-Sendable unsafe buffer views, and confine any opt-out to proven local base-pointer bindings. Every review that retains the parallel path must require benchmark-versus-serial, serial-result, and nested-parallelism checks.
@MainActorSendable conformance is correct (no unjustified @unchecked)@preconcurrency imports are documented with removal plan@concurrent, not @MainActor.task modifier used in SwiftUI instead of manual Task managementlifetime invariants
Other measured skills in the registry, with their headline benchmark lift.