Token导航 LogoToken导航TokenDH.com
开发需要联网github未标认证来源可访问许可证需确认审计通过

axiom-swift-concurrency-refaxiom Swift concurrency REF 命令行

Agent Skill

axiom-swift-concurrency-ref 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

2,117

周安装

90

GitHub Stars

873

下载量

742
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:axiom-swift-concurrency-ref(axiom Swift concurrency REF 命令行)
来源仓库:https://github.com/charleswiltgen/axiom
仓库路径:skills/axiom-swift-concurrency-ref
安装命令:
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swift-concurrency-ref
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swift-concurrency-ref

简介

提供 Swift 并发 API 参考,包含 actor、Task 和 async 函数签名。

  • 适用于 copy-paste 代码片段和语法快速查找场景。
  • 补充并发决策树,与同步原语技能形成完整参考体系。
  • 安装前建议确认权限范围和维护状态,避免触发联网或文件读写操作。
  • axiom-swift-concurrency-ref 属于开发类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

Swift Concurrency API Reference

Complete Swift concurrency API reference for copy-paste patterns and syntax lookup.

Complements axiom-swift-concurrency (which covers *when* and *why* to use concurrency — progressive journey, decision trees, @concurrent, isolated conformances).

Related skills: axiom-swift-concurrency (progressive journey, decision trees), axiom-synchronization (Mutex, locks), axiom-assume-isolated (assumeIsolated patterns)

Part 1: Actor Patterns

Actor Definition

actor ImageCache {
    private var cache: [URL: UIImage] = [:]

    func image(for url: URL) -> UIImage? {
        cache[url]
    }

    func store(_ image: UIImage, for url: URL) {
        cache[url] = image
    }
}

// Usage — must await across isolation boundary
let cache = ImageCache()
let image = await cache.image(for: url)

All properties and methods on an actor are isolated by default. Callers outside the actor's isolation domain must use await to access them.

Actor Isolation Rules

Every actor's stored properties and methods are isolated to that actor. Access from outside the isolation boundary requires await, which suspends the caller until the actor can process the request.

actor Counter {
    var count = 0              // Isolated — external access requires await
    let name: String           // let constants are implicitly nonisolated

    func increment() {         // Isolated — await required from outside
        count += 1
    }

    nonisolated func identity() -> String {
        name                   // OK: accessing nonisolated let
    }
}

let counter = Counter(name: "main")
await counter.increment()      // Must await across isolation boundary
let id = counter.identity()    // No await needed — nonisolated

nonisolated Keyword

Opt out of isolation for synchronous access to non-mutable state.

actor MyActor {
    let id: UUID               // let constants are implicitly nonisolated

    nonisolated var description: String {
        "Actor \(id)"          // Can only access nonisolated state
    }

    nonisolated func hash(into hasher: inout Hasher) {
        hasher.combine(id)     // Only nonisolated properties
    }
}

nonisolated methods cannot access any isolated stored properties. Use this for protocol conformances (like Hashable, CustomStringConvertible) that require synchronous access.

Actor Reentrancy

Suspension points (await) inside an actor allow other callers to interleave. State may change between any two await expressions.

actor BankAccount {
    var balance: Double = 0

    func transfer(amount: Double, to other: BankAccount) async {
        guard balance >= amount else { return }
        balance -= amount
        // REENTRANCY HAZARD: another caller could modify balance here
        // while we await the deposit on the other actor
        await other.deposit(amount)
    }

    func deposit(_ amount: Double) {
        balance += amount
    }
}

Pattern: Re-check state after every await inside an actor:

actor BankAccount {
    var balance: Double = 0

    func transfer(amount: Double, to other: BankAccount) async -> Bool {
        guard balance >= amount else { return false }
        balance -= amount

        await other.deposit(amount)

        // Re-check invariants after await if needed
        return true
    }
}

Global Actors

A global actor provides a single shared isolation domain accessible from anywhere.

@globalActor
actor MyGlobalActor {
    static let shared = MyGlobalActor()
}

@MyGlobalActor
func doWork() { /* isolated to MyGlobalActor */ }

@MyGlobalActor
class MyService {
    var state: Int = 0         // Isolated to MyGlobalActor
}

@MainActor

The built-in global actor for UI work. All UI updates must happen on @MainActor.

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

    func loadItems() async {
        let data = await fetchFromNetwork()
        items = data           // Safe: already on MainActor
    }
}

// Annotate individual members
class MixedService {
    @MainActor var uiState: String = ""

    @MainActor
    func updateUI() {
        uiState = "Done"
    }

    func backgroundWork() async -> String {
        await heavyComputation()
    }
}

Subclass inheritance: If a class is @MainActor, all subclasses inherit that isolation.

Actor Init

Actor initializers are NOT isolated to the actor. You cannot call isolated methods from init.

actor DataManager {
    var data: [String] = []

    init() {
        // Cannot call isolated methods here
        // self.loadDefaults()  // ERROR: actor-isolated method in non-isolated init
    }

    // Use a factory method instead
    static func create() async -> DataManager {
        let manager = DataManager()
        await manager.loadDefaults()
        return manager
    }

    func loadDefaults() {
        data = ["default"]
    }
}

Actor Gotcha Table

GotchaSymptomFix
Actor reentrancyState changes between awaitsRe-check state after each await
nonisolated accessing isolated stateCompiler errorRemove nonisolated or make property nonisolated
Calling actor method from sync context"Expression is 'async'"Wrap in Task {} or make caller async
Global actor inheritanceSubclass inherits @MainActorBe intentional about which methods need isolation
Actor init not isolatedCan't call isolated methods in initUse factory method or populate after init
Actor protocol conformance"Non-isolated" conformance errorUse nonisolated for protocol methods, or isolated conformance (Swift 6.2+)
Using actor for ViewModel@Published won't work, UI updates require awaitUse @MainActor class for UI-facing code, actor only for non-UI shared state
GCD queue-hopping inside actorBreaks isolation guarantees, risks thread explosionRemove GCD — actor isolation already serializes access

Part 2: Sendable Patterns

Automatic Sendable Conformance

Value types are Sendable when all stored properties are Sendable.

// Structs: Sendable when all stored properties are Sendable
struct UserProfile: Sendable {
    let name: String
    let age: Int
}

// Enums: Sendable when all associated values are Sendable
enum LoadState: Sendable {
    case idle
    case loading
    case loaded(String)        // String is Sendable
    case failed(Error)         // ERROR: Error is not Sendable
}

// Fix: use a Sendable error type
enum LoadState: Sendable {
    case idle
    case loading
    case loaded(String)
    case failed(any Error & Sendable)
}

@Sendable Closures

Closures passed across isolation boundaries must be @Sendable. A @Sendable closure cannot capture mutable local state.

func runInBackground(_ work: @Sendable () -> Void) {
    Task.detached { work() }
}

// All captured values must be Sendable
var count = 0
runInBackground {
    // ERROR: capture of mutable local variable
    // count += 1
}

let snapshot = count
runInBackground {
    print(snapshot)            // OK: let binding of Sendable type
}

@unchecked Sendable

Manual guarantee of thread safety. Use only when you provide synchronization yourself.

final class ThreadSafeCache: @unchecked Sendable {
    private let lock = NSLock()
    private var storage: [String: Any] = [:]

    func get(_ key: String) -> Any? {
        lock.lock()
        defer { lock.unlock() }
        return storage[key]
    }

    func set(_ key: String, value: Any) {
        lock.lock()
        defer { lock.unlock() }
        storage[key] = value
    }
}

Requirements for @unchecked Sendable

  • Class must be final
  • All mutable state must be protected by a synchronization primitive (lock, queue, Mutex)
  • You are responsible for correctness — the compiler will not check

Conditional Conformance

struct Box<T> {
    let value: T
}

// Box is Sendable only when T is Sendable
extension Box: Sendable where T: Sendable {}

// Standard library uses this extensively:
// Array<Element>: Sendable where Element: Sendable
// Dictionary<Key, Value>: Sendable where Key: Sendable, Value: Sendable
// Optional<Wrapped>: Sendable where Wrapped: Sendable

sending Parameter Modifier (SE-0430)

Transfer ownership of a value across isolation boundaries. The caller gives up access.

func process(_ value: sending String) async {
    // Caller can no longer access value after this call
    await store(value)
}

// Useful for transferring non-Sendable types when caller won't use them again
func handOff(_ connection: sending NetworkConnection) async {
    await manager.accept(connection)
}

Build Settings

Control the strictness of Sendable checking in Xcode:

SettingValueBehavior
SWIFT_STRICT_CONCURRENCYminimalOnly explicit Sendable annotations checked
SWIFT_STRICT_CONCURRENCYtargetedInferred Sendable + closure checking
SWIFT_STRICT_CONCURRENCYcompleteFull strict concurrency (Swift 6 default)

Sendable Gotcha Table

GotchaSymptomFix
Class can't be Sendable"Class cannot conform to Sendable"Make final + immutable, or @unchecked Sendable with locks
Closure captures non-Sendable"Capture of non-Sendable type"Copy value before capture, or make type Sendable
Protocol can't require SendableGeneric constraints complexUse where T: Sendable
@unchecked Sendable hides bugsData races at runtimeOnly use when lock/queue guarantees safety
Array/Dictionary conditionalCollection is Sendable only if Element isEnsure element types are Sendable
Error not Sendable"Type does not conform to Sendable"Use any Error & Sendable or typed errors

Part 3: Task Management

Task {}

Creates an unstructured task that inherits the current actor context and priority.

// Inherits actor context — if called from @MainActor, runs on MainActor
let task = Task {
    try await fetchData()
}

// Get the result
let result = try await task.value

// Get Result<Success, Failure>
let outcome = await task.result

Task.detached {}

Creates a task with no inherited context. Does not inherit the actor or priority.

Task.detached(priority: .background) {
    // NOT on MainActor even if created from MainActor
    await processLargeFile()
}

When to use: Background work that must NOT run on the calling actor. Prefer Task {} in most cases — Task.detached is rarely needed.

Task Cancellation

Cancellation is cooperative. Setting cancellation is a request; the task must check and respond.

let task = Task {
    for item in largeCollection {
        // Option 1: Check boolean
        if Task.isCancelled { break }

        // Option 2: Throw CancellationError
        try Task.checkCancellation()

        await process(item)
    }
}

// Request cancellation
task.cancel()

Task.sleep

Suspends the current task for a duration. Supports cancellation — throws CancellationError if cancelled during sleep.

// Duration-based (preferred)
try await Task.sleep(for: .seconds(2))
try await Task.sleep(for: .milliseconds(500))

// Nanoseconds (older API)
try await Task.sleep(nanoseconds: 2_000_000_000)

Task.yield

Voluntarily yields execution to allow other tasks to run. Use in long-running synchronous loops.

for i in 0..<1_000_000 {
    if i.isMultiple(of: 1000) {
        await Task.yield()
    }
    process(i)
}

Task Priority

PriorityUse Case
.userInitiatedDirect user action, visible result
.highSame as.userInitiated
.mediumDefault when not specified
.lowPrefetching, non-urgent work
.utilityLong computation, progress shown
.backgroundMaintenance, cleanup, not time-sensitive
Task(priority: .userInitiated) {
    await loadVisibleContent()
}

Task(priority: .background) {
    await cleanupTempFiles()
}

@TaskLocal

Task-scoped values that propagate to child tasks automatically.

enum RequestContext {
    @TaskLocal static var requestID: String?
    @TaskLocal static var userID: String?
}

// Set values for a scope
RequestContext.$requestID.withValue("req-123") {
    RequestContext.$userID.withValue("user-456") {
        // Both values available here and in child tasks
        Task {
            print(RequestContext.requestID)  // "req-123"
            print(RequestContext.userID)     // "user-456"
        }
    }
}

// Outside scope — values are nil
print(RequestContext.requestID)  // nil

Propagation rules: @TaskLocal values propagate to child tasks created with Task {}. They do NOT propagate to Task.detached {}.

Task Timeout Pattern

Enforce a deadline on any async operation using a task group race:

func withTimeout<T: Sendable>(
    _ duration: Duration,
    operation: @Sendable @escaping () async throws -> T
) async throws -> T {
    try await withThrowingTaskGroup(of: T.self) { group in
        group.addTask { try await operation() }
        group.addTask {
            try await Task.sleep(for: duration)
            throw TimeoutError()
        }
        guard let result = try await group.next() else {
            throw TimeoutError()
        }
        group.cancelAll()  // Cancel the loser — without this it keeps running
        return result
    }
}

group.cancelAll() is critical. Without it, the losing task (either the timeout or the operation) continues running until the group scope exits.

Task Retain Cycles

Tasks capture variables like closures. Stored tasks that reference self create retain cycles.

// ❌ Retain cycle: self → task → self
task = Task {
    while true { await self.poll() }
}

// ✅ Weak capture breaks the cycle
task = Task { [weak self] in
    while let self, !Task.isCancelled {
        await self.poll()
    }
}

Rule: Use [weak self] when the Task is stored as a property or iterates an infinite async sequence. Short-lived Tasks that complete quickly can use strong captures.

Thread.current in Swift 6

Thread.current is unavailable from async contexts in Swift 6 language mode:

// ❌ Compiler error in Swift 6 mode
func check() async { print(Thread.current) }

// ✅ Workaround for debugging only
extension Thread {
    static var currentThread: Thread { Thread.current }
}

Don't rely on thread identity for correctness — tasks move between threads at suspension points. Reason about isolation domains instead.

Task Gotcha Table

GotchaSymptomFix
Task never cancelledResource leak, work continues after view disappearsStore task, cancel in deinit/onDisappear
Ignoring cancellationTask runs to completion even when cancelledCheck Task.isCancelled in loops, use checkCancellation()
Task.detached loses actor context"Not isolated to MainActor"Use Task {} when you need actor isolation
Capturing self in stored TaskRetain cycle, deinit never calledUse [weak self] for long-lived or stored tasks
Assuming async = backgroundCode stays on calling actorUse @concurrent to force background execution
TaskLocal not propagatedValue is nil in detached taskTaskLocal only propagates to child tasks, not detached
Task priority inversionLow-priority task blocks high-prioritySystem handles most cases; avoid awaiting low-priority from high
Thread.current in async contextCompiler error in Swift 6 modeDon't rely on thread identity — use isolation domains

Part 4: Structured Concurrency

async let

Run a fixed number of operations in parallel. All async let bindings are implicitly awaited when the scope exits.

async let images = fetchImages()
async let metadata = fetchMetadata()
async let config = loadConfig()

// All three run concurrently, await together
let (imgs, meta, cfg) = try await (images, metadata, config)

Semantics: If one async let throws, the others are cancelled. All must complete (or be cancelled) before the enclosing scope exits.

TaskGroup — Non-Throwing

Dynamic number of parallel tasks where none throw.

let results = await withTaskGroup(of: String.self) { group in
    for name in names {
        group.addTask {
            await fetchGreeting(for: name)
        }
    }

    var greetings: [String] = []
    for await greeting in group {
        greetings.append(greeting)
    }
    return greetings
}

TaskGroup — Throwing

Dynamic number of parallel tasks that can throw.

let images = try await withThrowingTaskGroup(of: (URL, UIImage).self) { group in
    for url in urls {
        group.addTask {
            let image = try await downloadImage(url)
            return (url, image)
        }
    }

    var results: [URL: UIImage] = [:]
    for try await (url, image) in group {
        results[url] = image
    }
    return results
}

withDiscardingTaskGroup (iOS 17+)

For when you need concurrency but don't need to collect results. More memory-efficient than regular TaskGroup — no result storage.

try await withThrowingDiscardingTaskGroup { group in
    for connection in connections {
        group.addTask {
            try await connection.monitor()
            // Results are discarded — useful for long-running services
        }
    }
    // Group stays alive until all tasks complete or one throws
}

Real-world pattern — merge multiple notification streams

extension NotificationCenter {
    func notifications(named names: [Notification.Name]) -> AsyncStream<Void> {
        AsyncStream { continuation in
            let task = Task {
                await withDiscardingTaskGroup { group in
                    for name in names {
                        group.addTask {
                            for await _ in self.notifications(named: name) {
                                continuation.yield()
                            }
                        }
                    }
                }
                continuation.finish()
            }
            continuation.onTermination = { _ in task.cancel() }
        }
    }
}

TaskGroup Control

await withTaskGroup(of: Data.self) { group in
    // Add tasks conditionally
    group.addTaskUnlessCancelled {
        await fetchData()
    }

    // Cancel remaining tasks
    group.cancelAll()

    // Wait without collecting
    await group.waitForAll()

    // Iterate one at a time
    while let result = await group.next() {
        process(result)
    }
}

Task Tree Semantics

Structured concurrency forms a tree:

  • Parent cancellation cancels all children — cancelling a task cancels all async let and TaskGroup children
  • Child error propagates to parent — in throwing groups, a child error cancels siblings and propagates up
  • All children must complete before parent returns — the scope awaits all children, even cancelled ones
// If fetchImages() throws, fetchMetadata() is automatically cancelled
async let images = fetchImages()
async let metadata = fetchMetadata()
let result = try await (images, metadata)

Structured Concurrency Gotcha Table

GotchaSymptomFix
async let unusedWork still executes but result is discarded silentlyAssign all async let results or use withDiscardingTaskGroup
TaskGroup accumulating memoryMemory grows with 10K+ tasksProcess results as they arrive, don't collect all
Capturing mutable state in addTask"Mutation of captured var"Use let binding or actor
Not handling partial failureSome tasks succeed, some failUse group.next() and handle errors individually
async let in loopCompiler error — async let must be in fixed positionsUse TaskGroup instead
Returning from group earlyRemaining tasks still runCall group.cancelAll() before returning

Part 5: Async Sequences

AsyncStream

Non-throwing stream for producing values over time.

let stream = AsyncStream<Int> { continuation in
    for i in 0..<10 {
        continuation.yield(i)
    }
    continuation.finish()
}

for await value in stream {
    print(value)
}

AsyncThrowingStream

Stream that can fail with an error.

let stream = AsyncThrowingStream<Data, Error> { continuation in
    let monitor = NetworkMonitor()
    monitor.onData = { data in
        continuation.yield(data)
    }
    monitor.onError = { error in
        continuation.finish(throwing: error)
    }
    monitor.onComplete = {
        continuation.finish()
    }

    continuation.onTermination = { @Sendable _ in
        monitor.stop()
    }

    monitor.start()
}

do {
    for try await data in stream {
        process(data)
    }
} catch {
    handleStreamError(error)
}

Continuation API

let stream = AsyncStream<Value> { continuation in
    // Emit a value
    continuation.yield(value)

    // End the stream normally
    continuation.finish()

    // Cleanup when consumer cancels or stream ends
    continuation.onTermination = { @Sendable termination in
        switch termination {
        case .cancelled:
            cleanup()
        case .finished:
            finalCleanup()
        @unknown default:
            break
        }
    }
}

// For throwing streams
let stream = AsyncThrowingStream<Value, Error> { continuation in
    continuation.yield(value)
    continuation.finish()                  // Normal end
    continuation.finish(throwing: error)   // End with error
}

Buffering Policies

Control what happens when values are produced faster than consumed.

// Keep all values (default) — memory can grow unbounded
let stream = AsyncStream<Int>(bufferingPolicy: .unbounded) { continuation in
    // ...
}

// Keep oldest N values, drop new ones when buffer is full
let stream = AsyncStream<Int>(bufferingPolicy: .bufferingOldest(100)) { continuation in
    // ...
}

// Keep newest N values, drop old ones when buffer is full
let stream = AsyncStream<Int>(bufferingPolicy: .bufferingNewest(100)) { continuation in
    // ...
}
PolicyBehaviorUse When
.unboundedKeeps all valuesConsumer keeps up, or bounded producer
.bufferingOldest(N)Drops new values when fullOrder matters, older values have priority
.bufferingNewest(N)Drops old values when fullLatest state matters (UI updates, sensor data)

Custom AsyncSequence

struct Counter: AsyncSequence {
    typealias Element = Int
    let limit: Int

    struct AsyncIterator: AsyncIteratorProtocol {
        var current = 0
        let limit: Int

        mutating func next() async -> Int? {
            guard current < limit else { return nil }
            defer { current += 1 }
            return current
        }
    }

    func makeAsyncIterator() -> AsyncIterator {
        AsyncIterator(limit: limit)
    }
}

// Usage
for await number in Counter(limit: 5) {
    print(number)  // 0, 1, 2, 3, 4
}

AsyncSequence Operators

Standard operators work on any AsyncSequence:

// Map
for await name in users.map(\.name) { }

// Filter
for await adult in users.filter({ $0.age >= 18 }) { }

// CompactMap
for await image in urls.compactMap({ await tryLoadImage($0) }) { }

// Prefix
for await first5 in stream.prefix(5) { }

// first(where:)
let match = await stream.first(where: { $0 > threshold })

// Contains
let hasMatch = await stream.contains(where: { $0 > threshold })

// Reduce
let sum = await numbers.reduce(0, +)

Built-in Async Sequences

// NotificationCenter
for await notification in NotificationCenter.default.notifications(named: .didUpdate) {
    handleUpdate(notification)
}

// URLSession bytes
let (bytes, response) = try await URLSession.shared.bytes(from: url)
for try await byte in bytes {
    process(byte)
}

// FileHandle bytes
for try await line in FileHandle.standardInput.bytes.lines {
    process(line)
}

Async Sequence Gotcha Table

GotchaSymptomFix
Continuation yielded after finishRuntime warning, value lostTrack finished state, guard before yield
Stream never finishingfor-await loop hangs foreverAlways call continuation.finish() in all code paths
No onTermination handlerResource leak when consumer cancelsSet continuation.onTermination for cleanup
Unbounded bufferMemory growth under loadUse.bufferingNewest(N) or.bufferingOldest(N)
Multiple consumersOnly first consumer gets valuesAsyncStream is single-consumer; create separate streams per consumer
for-await on MainActorUI freezes waiting for valuesUse Task {} to consume off the main path

Part 6: Isolation Patterns

@MainActor on Functions

@MainActor
func updateUI() {
    label.text = "Done"
}

// Call from async context
func doWork() async {
    let result = await computeResult()
    await updateUI()           // Hops to MainActor
}

MainActor.run

Explicitly execute a closure on the main actor from any context.

func processData() async {
    let result = await heavyComputation()

    await MainActor.run {
        self.label.text = result
        self.progressView.isHidden = true
    }
}

MainActor.assumeIsolated (iOS 17+)

Assert that code is already running on the main actor. Crashes at runtime if the assertion is false.

func legacyCallback() {
    // We KNOW this is called on main thread (UIKit guarantee)
    MainActor.assumeIsolated {
        self.viewModel.update()    // Access @MainActor state
    }
}

See axiom-assume-isolated for comprehensive patterns.

nonisolated

Opt out of the enclosing actor's isolation.

@MainActor
class ViewModel {
    let id: UUID                           // Implicitly nonisolated (let)

    nonisolated var analyticsID: String {   // Explicitly nonisolated
        id.uuidString
    }

    var items: [Item] = []                 // Isolated to MainActor
}

nonisolated(unsafe)

Compiler escape hatch. Tells the compiler to treat a property as if it's not isolated, without any safety guarantees.

// Use only when you have external guarantees of thread safety
nonisolated(unsafe) var legacyState: Int = 0

// Common for global constants that the compiler can't verify
nonisolated(unsafe) let formatter: DateFormatter = {
    let f = DateFormatter()
    f.dateStyle = .medium
    return f
}()

Warning: nonisolated(unsafe) provides zero runtime protection. Data races will not be caught. Use only as a last resort for bridging legacy code.

@preconcurrency

Suppress concurrency warnings for pre-concurrency APIs during migration.

// Suppress warnings for entire module
@preconcurrency import MyLegacyFramework

// Suppress for specific protocol conformance
class MyDelegate: @preconcurrency SomeLegacyDelegate {
    func delegateCallback() {
        // No Sendable warnings for this conformance
    }
}

#isolation (Swift 6.0+)

Capture the caller's isolation context so a function runs on whatever actor the caller is on.

func doWork(isolation: isolated (any Actor)? = #isolation) async {
    // Runs on caller's actor — no hop if caller is already isolated
    performWork()
}

// Called from @MainActor — runs on MainActor
@MainActor
func setup() async {
    await doWork()             // doWork runs on MainActor
}

// Called from custom actor — runs on that actor
actor MyActor {
    func run() async {
        await doWork()         // doWork runs on MyActor
    }
}

#isolation Capture in Task Closures (SE-0420)

When spawning Task closures that need to work with non-Sendable types, capture the isolation parameter to inherit the caller's context.

func process(
    delegate: NonSendableDelegate,
    isolation: isolated (any Actor)? = #isolation
) {
    Task {
        _ = isolation          // Forces capture — Task inherits caller's isolation
        delegate.doWork()      // ✅ Safe: running on caller's actor
    }
}

Why _ = isolation is required: Per SE-0420, Task closures only inherit isolation when a non-optional binding of an isolated parameter is captured by the closure. The _ = isolation statement forces this capture. Without it, the Task runs on the default executor and the non-Sendable capture is a compiler error.

When to use: Spawning Tasks that work with non-Sendable delegate objects, fire-and-forget async work that needs access to caller's state, or bridging callback-based APIs while keeping delegates alive.

Isolation Gotcha Table

GotchaSymptomFix
MainActor.run from MainActorUnnecessary hop, potential deadlock riskCheck context or use assumeIsolated
nonisolated(unsafe) data raceCrash at runtime, corrupted stateUse proper isolation or Mutex
@preconcurrency hiding real issuesRuntime crashes in productionMigrate to proper concurrency before shipping
#isolation not available pre-5.9Compiler errorUse traditional @MainActor annotation
#isolation not captured in TaskNon-Sendable capture errorAdd _ = isolation inside Task closure (SE-0420)
nonisolated on actor methodCan't access any isolated stateOnly use for computed properties from non-isolated state
Thread.current in async contextCompiler error in Swift 6 modeDon't rely on thread identity — reason about isolation domains

Part 7: Continuations

Bridge callback-based APIs to async/await.

withCheckedContinuation

Non-throwing bridge.

func currentLocation() async -> CLLocation {
    await withCheckedContinuation { continuation in
        locationManager.requestLocation { location in
            continuation.resume(returning: location)
        }
    }
}

withCheckedThrowingContinuation

Throwing bridge.

func fetchUser(id: String) async throws -> User {
    try await withCheckedThrowingContinuation { continuation in
        api.fetchUser(id: id) { result in
            switch result {
            case .success(let user):
                continuation.resume(returning: user)
            case .failure(let error):
                continuation.resume(throwing: error)
            }
        }
    }
}

Continuation Resume Methods

// Return a value
continuation.resume(returning: value)

// Throw an error
continuation.resume(throwing: error)

// From a Result type
continuation.resume(with: result)    // Result<T, Error>

Resume-Exactly-Once Rule

A continuation MUST be resumed exactly once:

  • Resuming twice crashes with "Continuation already resumed" (checked) or undefined behavior (unsafe)
  • Never resuming causes the awaiting task to hang forever — a silent leak
// DANGEROUS: callback might not be called
func riskyBridge() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        api.fetch { data, error in
            if let error {
                continuation.resume(throwing: error)
                return
            }
            if let data {
                continuation.resume(returning: data)
                return
            }
            // BUG: if both are nil, continuation is never resumed
            // Fix: add a fallback
            continuation.resume(throwing: BridgeError.noResponse)
        }
    }
}

Bridging Delegates

class LocationBridge: NSObject, CLLocationManagerDelegate {
    private var continuation: CheckedContinuation<CLLocation, Error>?
    private let manager = CLLocationManager()

    func requestLocation() async throws -> CLLocation {
        try await withCheckedThrowingContinuation { continuation in
            self.continuation = continuation
            manager.delegate = self
            manager.requestLocation()
        }
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        continuation?.resume(returning: locations[0])
        continuation = nil     // Prevent double resume
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        continuation?.resume(throwing: error)
        continuation = nil
    }
}

Unsafe Continuations

Skip runtime checks for performance. Same API as checked, but misuse causes undefined behavior instead of a diagnostic crash.

func fastBridge() async -> Data {
    await withUnsafeContinuation { continuation in
        // No runtime check for double-resume or missing resume
        fastCallback { data in
            continuation.resume(returning: data)
        }
    }
}

Use checked continuations during development, switch to unsafe only after thorough testing and when profiling shows the check is a bottleneck.

Continuation Gotcha Table

GotchaSymptomFix
Resume called twice"Continuation already resumed" crashSet continuation to nil after resume
Resume never calledTask hangs indefinitelyEnsure all code paths resume — including error/nil cases
Capturing continuationContinuation escapes scopeStore in property, ensure single resume
Unsafe continuation in debugNo diagnostics for misuseUse withCheckedContinuation during development
Delegate called multiple timesCrash on second resumeUse AsyncStream instead of continuation for repeated callbacks
Callback on wrong threadDoesn't matter for continuationContinuations can be resumed from any thread

Part 8: Migration Patterns

Common migrations from GCD and completion handlers to Swift concurrency.

DispatchQueue to Actor

// BEFORE: DispatchQueue for thread safety
class ImageCache {
    private let queue = DispatchQueue(label: "cache", attributes: .concurrent)
    private var cache: [URL: UIImage] = [:]

    func get(_ url: URL, completion: @escaping (UIImage?) -> Void) {
        queue.async { completion(self.cache[url]) }
    }

    func set(_ url: URL, image: UIImage) {
        queue.async(flags: .barrier) { self.cache[url] = image }
    }
}

// AFTER: Actor
actor ImageCache {
    private var cache: [URL: UIImage] = [:]

    func get(_ url: URL) -> UIImage? {
        cache[url]
    }

    func set(_ url: URL, image: UIImage) {
        cache[url] = image
    }
}

DispatchGroup to TaskGroup

// BEFORE: DispatchGroup
let group = DispatchGroup()
var results: [Data] = []
for url in urls {
    group.enter()
    fetch(url) { data in
        results.append(data)
        group.leave()
    }
}
group.notify(queue: .main) { use(results) }

// AFTER: TaskGroup
let results = await withTaskGroup(of: Data.self) { group in
    for url in urls {
        group.addTask { await fetch(url) }
    }
    var collected: [Data] = []
    for await data in group {
        collected.append(data)
    }
    return collected
}
use(results)

Completion Handler to async

// BEFORE
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, _, error in
        if let error { completion(.failure(error)); return }
        guard let data else { completion(.failure(FetchError.noData)); return }
        completion(.success(data))
    }.resume()
}

// AFTER
func fetchData() async throws -> Data {
    let (data, _) = try await URLSession.shared.data(from: url)
    return data
}

@objc Delegates with @MainActor

@MainActor
class ViewController: UIViewController, UITableViewDelegate {
    // @objc delegate methods inherit @MainActor isolation from the class
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // Already on MainActor — safe to update UI
        updateSelection(indexPath)
    }
}

NotificationCenter to AsyncSequence

// BEFORE
let observer = NotificationCenter.default.addObserver(
    forName: .didUpdate, object: nil, queue: .main
) { notification in
    handleUpdate(notification)
}
// Must remove observer in deinit

// AFTER
let task = Task {
    for await notification in NotificationCenter.default.notifications(named: .didUpdate) {
        await handleUpdate(notification)
    }
}
// Cancel task in deinit — no manual observer removal needed

Timer to AsyncSequence

// BEFORE
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
    updateUI()
}
// Must invalidate in deinit

// AFTER
let task = Task {
    while !Task.isCancelled {
        await updateUI()
        try? await Task.sleep(for: .seconds(1))
    }
}
// Cancel task in deinit

DispatchSemaphore to Actor

// BEFORE: Semaphore to limit concurrent operations
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
    DispatchQueue.global().async {
        semaphore.wait()
        defer { semaphore.signal() }
        download(url)
    }
}

// AFTER: TaskGroup with limited concurrency
await withTaskGroup(of: Void.self) { group in
    var inFlight = 0
    for url in urls {
        if inFlight >= 3 {
            await group.next()   // Wait for one to finish
            inFlight -= 1
        }
        group.addTask { await download(url) }
        inFlight += 1
    }
    await group.waitForAll()
}

Migration Gotcha Table

GotchaSymptomFix
DispatchQueue.sync to actorDeadlock potentialRemove.sync, use await
Global dispatch to actor contentionSlowdown from serializationProfile with Concurrency Instruments
Legacy delegate + Sendable"Cannot conform to Sendable"Use @preconcurrency import or @MainActor isolation
Callback called multiple timesContinuation crashUse AsyncStream instead of continuation
Semaphore.wait in async contextThread starvation, potential deadlockUse TaskGroup with manual concurrency limiting
DispatchQueue.main.async to MainActorSubtle timing differencesMainActor.run is the equivalent — test edge cases
Replacing structured tasks with top-level TasksLosing cancellation propagation and error handlingUse async let or TaskGroup for related parallel work
Batch @unchecked Sendable to fix warningsHiding real data races throughout codebaseFix one type at a time with proper Sendable, actor, or sending

API Quick Reference

TaskAPISwift Version
Define isolated typeactor MyActor {}5.5+
Run on main thread@MainActor5.5+
Mark as safe to share: Sendable5.5+
Mark closure safe to share@Sendable5.5+
Parallel tasks (fixed)async let5.5+
Parallel tasks (dynamic)withTaskGroup5.5+
Stream valuesAsyncStream5.5+
Bridge callbackwithCheckedContinuation5.5+
Check cancellationTask.checkCancellation()5.5+
Task-scoped values@TaskLocal5.5+
Assert isolationMainActor.assumeIsolated5.9+ (iOS 17+)
Capture caller isolation#isolation6.0+
Lock-based syncMutex6.0+ (iOS 18+)
Discard resultswithDiscardingTaskGroup5.9+ (iOS 17+)
Transfer ownershipsending parameter6.0+
Force background@concurrent6.2+
Isolated conformanceextension: @MainActor Proto6.2+

Resources

WWDC: 2021-10132, 2021-10134, 2022-110350, 2025-268

Docs: /swift/concurrency, /swift/actor, /swift/sendable, /swift/taskgroup

Skills: swift-concurrency, assume-isolated, synchronization, concurrency-profiling

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

33.13%
按下载量换算246

Claude

30.04%
按下载量换算223

Cursor

17.63%
按下载量换算131

Gemini CLI

10.28%
按下载量换算76

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills