swift-concurrency

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Swift Concurrency

Swift 并发编程

Lifecycle Position

生命周期定位

Phase 3 (Implement) and Phase 5 (Review). Load for async patterns during implementation; use checklist during code review. Related:
swift-networking
for async network calls.
第3阶段(实现)和第5阶段(评审)。在实现阶段学习异步模式;在代码评审阶段使用检查清单。相关技能:
swift-networking
(用于异步网络请求)。

Decision Tree: Where Should This Code Run?

决策树:代码应在何处运行?

Is it UI code (views, view models, UI state)?
  → Yes → @MainActor
Does it manage shared mutable state?
  → Yes → Custom actor
Is it pure computation with no shared state?
  → Yes → nonisolated (Swift 6.2: @concurrent for CPU-heavy work)
Is it a one-off async operation from synchronous code?
  → Yes → Task { } (inherits actor context)
Is it UI code (views, view models, UI state)?
  → Yes → @MainActor
Does it manage shared mutable state?
  → Yes → Custom actor
Is it pure computation with no shared state?
  → Yes → nonisolated (Swift 6.2: @concurrent for CPU-heavy work)
Is it a one-off async operation from synchronous code?
  → Yes → Task { } (inherits actor context)

Actor Isolation

Actor 隔离

@MainActor

@MainActor

swift
// On the entire class (recommended for ViewModels)
@Observable @MainActor
class UserViewModel {
    var users: [User] = []
    var isLoading = false

    func loadUsers() async {
        isLoading = true
        defer { isLoading = false }
        users = try? await api.fetchUsers() ?? []
    }
}

// On specific methods only
class DataProcessor {
    @MainActor func updateUI(with results: [Result]) {
        // Safe to touch UI state
    }

    nonisolated func processInBackground(_ data: Data) -> [Result] {
        // Runs on any thread, no actor isolation
    }
}
swift
// On the entire class (recommended for ViewModels)
@Observable @MainActor
class UserViewModel {
    var users: [User] = []
    var isLoading = false

    func loadUsers() async {
        isLoading = true
        defer { isLoading = false }
        users = try? await api.fetchUsers() ?? []
    }
}

// On specific methods only
class DataProcessor {
    @MainActor func updateUI(with results: [Result]) {
        // Safe to touch UI state
    }

    nonisolated func processInBackground(_ data: Data) -> [Result] {
        // Runs on any thread, no actor isolation
    }
}

Custom Actors

自定义Actor

swift
actor ImageCache {
    private var cache: [URL: Image] = [:]

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

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

// Usage — every call crosses an isolation boundary
let cache = ImageCache()
let image = await cache.image(for: url)  // await required
swift
actor ImageCache {
    private var cache: [URL: Image] = [:]

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

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

// Usage — every call crosses an isolation boundary
let cache = ImageCache()
let image = await cache.image(for: url)  // await required

Actor Reentrancy

Actor 可重入性

swift
actor BankAccount {
    var balance: Decimal

    func withdraw(_ amount: Decimal) async throws {
        // WARNING: balance might change between check and deduction
        // because another task could run during the await
        guard balance >= amount else { throw BankError.insufficientFunds }

        await recordTransaction(amount)  // suspension point — other tasks can interleave

        // Re-check invariant after suspension
        guard balance >= amount else { throw BankError.insufficientFunds }
        balance -= amount
    }
}
Rule: Always re-validate state after any
await
inside an actor.
swift
actor BankAccount {
    var balance: Decimal

    func withdraw(_ amount: Decimal) async throws {
        // WARNING: balance might change between check and deduction
        // because another task could run during the await
        guard balance >= amount else { throw BankError.insufficientFunds }

        await recordTransaction(amount)  // suspension point — other tasks can interleave

        // Re-check invariant after suspension
        guard balance >= amount else { throw BankError.insufficientFunds }
        balance -= amount
    }
}
规则: 在Actor内部的任何
await
操作后,必须重新验证状态。

SwiftData Concurrency

SwiftData 并发

@Model
classes have specific concurrency constraints enforced by Swift 6:
  • @Model
    is non-Sendable
    — do not pass model instances across actor boundaries
  • ModelContext
    is
    @MainActor
    -bound
    — create and use on the main actor
  • ModelContainer
    is Sendable
    — safe to pass between actors
@Model
类有Swift 6强制执行的特定并发约束:
  • @Model
    不遵循Sendable
    — 不要跨Actor边界传递模型实例
  • ModelContext
    绑定到
    @MainActor
    — 在主Actor上创建和使用
  • ModelContainer
    遵循Sendable
    — 可安全在Actor之间传递

Cross-Actor Access Pattern

跨Actor访问模式

swift
// WRONG — passing @Model across actors
let item = context.fetch(...)  // on MainActor
await backgroundActor.process(item)  // non-Sendable error

// RIGHT — pass the identifier, re-fetch on the other side
let itemID = item.persistentModelID
await backgroundActor.process(itemID, container: container)

// In the background actor:
let bgContext = ModelContext(container)
let item = bgContext.model(for: itemID) as? Item
swift
// WRONG — passing @Model across actors
let item = context.fetch(...)  // on MainActor
await backgroundActor.process(item)  // non-Sendable error

// RIGHT — pass the identifier, re-fetch on the other side
let itemID = item.persistentModelID
await backgroundActor.process(itemID, container: container)

// In the background actor:
let bgContext = ModelContext(container)
let item = bgContext.model(for: itemID) as? Item

Testing Implications

测试注意事项

When writing tests for
@Observable @MainActor
models that use SwiftData:
  • Test suites must be
    @MainActor
    (matches the model's isolation)
  • Use
    .serialized
    on
    @Suite
    to prevent parallel container conflicts
  • See
    ios-testing
    skill for complete SwiftData testing patterns
为使用SwiftData的
@Observable @MainActor
模型编写测试时:
  • 测试套件必须标记为
    @MainActor
    (与模型的隔离级别匹配)
  • @Suite
    上使用
    .serialized
    以避免并行容器冲突
  • 完整的SwiftData测试模式请参考
    ios-testing
    技能

Sendability

Sendability

Sendable Protocol

Sendable 协议

swift
// Value types are automatically Sendable
struct UserID: Sendable { let rawValue: UUID }

// Immutable classes can be Sendable
final class Configuration: Sendable {
    let apiURL: URL        // let = immutable = safe
    let timeout: TimeInterval
}

// Mutable shared state needs an actor, not @unchecked Sendable
// AVOID unless you truly manage synchronization yourself:
final class LegacyCache: @unchecked Sendable {
    private let lock = NSLock()
    private var storage: [String: Data] = [:]
    // Must manually synchronize ALL access
}
swift
// Value types are automatically Sendable
struct UserID: Sendable { let rawValue: UUID }

// Immutable classes can be Sendable
final class Configuration: Sendable {
    let apiURL: URL        // let = immutable = safe
    let timeout: TimeInterval
}

// Mutable shared state needs an actor, not @unchecked Sendable
// AVOID unless you truly manage synchronization yourself:
final class LegacyCache: @unchecked Sendable {
    private let lock = NSLock()
    private var storage: [String: Data] = [:]
    // Must manually synchronize ALL access
}

The
sending
Keyword (Swift 6)

sending
关键字(Swift 6)

swift
func process(_ data: sending Data) async {
    // Compiler guarantees caller won't access data after passing it
}
swift
func process(_ data: sending Data) async {
    // Compiler guarantees caller won't access data after passing it
}

Structured Concurrency

结构化并发

async let (parallel independent work)

async let(并行独立任务)

swift
async let profile = api.fetchProfile(id)
async let posts = api.fetchPosts(userId: id)
async let followers = api.fetchFollowers(userId: id)

let (p, po, f) = try await (profile, posts, followers)  // all run in parallel
swift
async let profile = api.fetchProfile(id)
async let posts = api.fetchPosts(userId: id)
async let followers = api.fetchFollowers(userId: id)

let (p, po, f) = try await (profile, posts, followers)  // all run in parallel

TaskGroup (dynamic parallel work)

TaskGroup(动态并行任务)

swift
let images = try await withThrowingTaskGroup(of: (URL, Image).self) { group in
    for url in urls {
        group.addTask {
            let image = try await downloadImage(from: url)
            return (url, image)
        }
    }
    var results: [URL: Image] = [:]
    for try await (url, image) in group {
        results[url] = image
    }
    return results
}
swift
let images = try await withThrowingTaskGroup(of: (URL, Image).self) { group in
    for url in urls {
        group.addTask {
            let image = try await downloadImage(from: url)
            return (url, image)
        }
    }
    var results: [URL: Image] = [:]
    for try await (url, image) in group {
        results[url] = image
    }
    return results
}

Cancellation

取消操作

swift
func processLargeDataset(_ items: [Item]) async throws {
    for item in items {
        try Task.checkCancellation()  // Throws if cancelled
        await process(item)
    }
}

// Or check without throwing
if Task.isCancelled { return }
swift
func processLargeDataset(_ items: [Item]) async throws {
    for item in items {
        try Task.checkCancellation()  // Throws if cancelled
        await process(item)
    }
}

// Or check without throwing
if Task.isCancelled { return }

Unstructured Tasks

非结构化任务

swift
// Task { } — inherits current actor context
@MainActor class ViewModel {
    func start() {
        Task {
            // This runs on MainActor (inherited)
            await loadData()
        }
    }
}

// Task.detached { } — no actor inheritance, runs on global executor
Task.detached(priority: .background) {
    // This does NOT run on MainActor
    let result = heavyComputation(data)
    await MainActor.run { self.updateUI(result) }
}
When to use each:
  • Task { }
    — 90% of cases. Starting async work from sync context.
  • Task.detached { }
    — CPU-heavy work that must NOT run on MainActor.
swift
// Task { } — inherits current actor context
@MainActor class ViewModel {
    func start() {
        Task {
            // This runs on MainActor (inherited)
            await loadData()
        }
    }
}

// Task.detached { } — no actor inheritance, runs on global executor
Task.detached(priority: .background) {
    // This does NOT run on MainActor
    let result = heavyComputation(data)
    await MainActor.run { self.updateUI(result) }
}
使用场景:
  • Task { }
    — 90%的场景,从同步上下文启动异步任务。
  • Task.detached { }
    — 必须不在主Actor上运行的CPU密集型任务。

Migration from GCD

从GCD迁移

GCD PatternModern Equivalent
DispatchQueue.main.async { }
@MainActor
or
MainActor.run { }
DispatchQueue.global().async { }
Task.detached { }
or
nonisolated
method
DispatchGroup
TaskGroup
or
async let
DispatchSemaphore
Actor isolation (semaphores + async = deadlock risk)
DispatchQueue
(serial, for sync)
Actor
Completion handler
async throws ->
return value
GCD 模式现代替代方案
DispatchQueue.main.async { }
@MainActor
MainActor.run { }
DispatchQueue.global().async { }
Task.detached { }
nonisolated
方法
DispatchGroup
TaskGroup
async let
DispatchSemaphore
Actor隔离(信号量+异步=死锁风险)
DispatchQueue
(串行,用于同步)
Actor
完成回调
async throws ->
返回值

Bridging Completion Handlers

桥接完成回调

swift
func legacyFetch(completion: @escaping (Result<Data, Error>) -> Void) { /* ... */ }

// Wrap with continuation
func modernFetch() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        legacyFetch { result in
            continuation.resume(with: result)  // MUST be called exactly once
        }
    }
}
swift
func legacyFetch(completion: @escaping (Result<Data, Error>) -> Void) { /* ... */ }

// Wrap with continuation
func modernFetch() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        legacyFetch { result in
            continuation.resume(with: result)  // MUST be called exactly once
        }
    }
}

Swift 6 Strict Concurrency

Swift 6 严格并发

Incremental Adoption

增量采用

swift
// Enable per-target in Package.swift
.target(name: "MyApp", swiftSettings: [.swiftLanguageMode(.v6)])

// Or per-file: suppress specific warnings during migration
@preconcurrency import SomeOldLibrary
swift
// Enable per-target in Package.swift
.target(name: "MyApp", swiftSettings: [.swiftLanguageMode(.v6)])

// Or per-file: suppress specific warnings during migration
@preconcurrency import SomeOldLibrary

Common Warnings and Fixes

常见警告与修复方案

WarningFix
"Sending 'value' risks data race"Make type
Sendable
or use
sending
parameter
"Non-sendable type captured by @Sendable closure"Move to actor-isolated method or make type Sendable
"Actor-isolated property cannot be mutated from nonisolated context"Add
await
or annotate caller with same actor
"Global variable is not concurrency-safe"Make it
let
, move to actor, or use
nonisolated(unsafe)
(last resort)
"Conformance of X to protocol Y crosses into main actor-isolated code"Use isolated conformance:
extension X: @MainActor Y
(Swift 6.2+)
警告修复方法
"Sending 'value' risks data race"让类型遵循
Sendable
或使用
sending
参数
"Non-sendable type captured by @Sendable closure"移至Actor隔离方法或让类型遵循Sendable
"Actor-isolated property cannot be mutated from nonisolated context"添加
await
或为调用者标记相同的Actor
"Global variable is not concurrency-safe"设为
let
、移至Actor,或使用
nonisolated(unsafe)
(最后手段)
"Conformance of X to protocol Y crosses into main actor-isolated code"使用隔离协议一致性:
extension X: @MainActor Y
(Swift 6.2+)

Swift 6.2 Approachable Concurrency

Swift 6.2 易用并发

Swift 6.2 changes the default: async functions stay on the calling actor instead of hopping to the global concurrent executor. This eliminates entire categories of data race errors for UI-bound code.
Key changes: See
references/swift-6-2-changes.md
for full details.
FeatureWhat It Does
Async stays on caller's actorNo implicit background hop — eliminates "Sending X risks data races" errors
@concurrent
attribute
Explicit opt-in to run on concurrent pool (replaces
Task.detached
for this use case)
Main-actor-by-default modeOpt-in: all types in a target implicitly
@MainActor
Isolated conformances
extension Foo: @MainActor Bar
— protocol conformance restricted to main actor
SwiftUI-specific: See
references/swiftui-concurrency.md
for off-main-thread APIs (
Shape
,
Layout
,
visualEffect
,
onGeometryChange
) and their
Sendable
closure requirements.
Project triage: Before diagnosing concurrency errors, check Xcode Build Settings → Swift Compiler → Concurrency for language version and default actor isolation settings.
Swift 6.2修改了默认行为:异步函数停留在调用者的Actor上,而非跳转到全局并发执行器。这消除了UI相关代码中整类数据竞争错误。
主要变化: 完整详情请参考
references/swift-6-2-changes.md
特性功能说明
异步函数保留在调用者Actor无隐式后台跳转——消除"Sending X risks data races"错误
@concurrent
属性
显式选择加入并发池运行(替代此场景下的
Task.detached
默认主Actor模式可选启用:目标中的所有类型隐式标记为
@MainActor
隔离协议一致性
extension Foo: @MainActor Bar
——协议一致性限制在主Actor上
SwiftUI特定内容: 非主线程API(
Shape
Layout
visualEffect
onGeometryChange
)及其
Sendable
闭包要求请参考
references/swiftui-concurrency.md
项目诊断: 在诊断并发错误前,检查Xcode构建设置→Swift编译器→并发中的语言版本和默认Actor隔离设置。

Common Patterns

常见模式

Actor-Isolated Repository

Actor隔离的仓库

For a complete actor-based persistence pattern with file backing, see
swift-actor-persistence
.
swift
actor UserRepository {
    private let api: any APIClientProtocol
    private var cache: [UserID: User] = [:]

    func user(_ id: UserID) async throws -> User {
        if let cached = cache[id] { return cached }
        let user: User = try await api.request("/users/\(id.rawValue)")
        cache[id] = user
        return user
    }
}
带文件存储的完整Actor持久化模式请参考
swift-actor-persistence
swift
actor UserRepository {
    private let api: any APIClientProtocol
    private var cache: [UserID: User] = [:]

    func user(_ id: UserID) async throws -> User {
        if let cached = cache[id] { return cached }
        let user: User = try await api.request("/users/\(id.rawValue)")
        cache[id] = user
        return user
    }
}

.task Modifier for View Lifecycle

用于视图生命周期的.task修饰符

swift
struct UserView: View {
    @State private var viewModel = UserViewModel()

    var body: some View {
        content
            .task { await viewModel.load() }          // auto-cancels on disappear
            .task(id: selectedID) { await viewModel.load(selectedID) }  // re-runs when ID changes
    }
}
swift
struct UserView: View {
    @State private var viewModel = UserViewModel()

    var body: some View {
        content
            .task { await viewModel.load() }          // 视图消失时自动取消
            .task(id: selectedID) { await viewModel.load(selectedID) }  // ID变化时重新执行
    }
}

Review Checklist

评审检查清单

  • No data races: mutable shared state in actors or protected by isolation
  • Actor isolation boundaries clearly documented (which actor owns which state)
  • Cancellation cooperative: long operations check
    Task.checkCancellation()
  • Task
    references stored for cleanup if not using
    .task
    modifier
  • .task
    modifier used for view lifecycle async work (not
    onAppear { Task { } }
    )
  • @MainActor
    on view models and UI state classes
  • No
    DispatchSemaphore
    mixed with async code (deadlock risk)
  • withCheckedContinuation
    resumes exactly once (not zero, not twice)
  • State re-validated after suspension points in actors (reentrancy)
  • @unchecked Sendable
    justified in comment if used
  • Swift 6.2: Check project concurrency settings before diagnosing (language version, default actor isolation)
  • Swift 6.2:
    @concurrent
    used instead of
    Task.detached
    for explicit background work
  • SwiftUI: No
    @MainActor
    state accessed from
    Sendable
    closures (visualEffect, onGeometryChange) — use value copies
  • 无数据竞争:可变共享状态在Actor中或受隔离保护
  • Actor隔离边界清晰文档化(哪个Actor拥有哪个状态)
  • 协作式取消:长时操作检查
    Task.checkCancellation()
  • 若未使用
    .task
    修饰符,需存储
    Task
    引用以进行清理
  • 视图生命周期异步任务使用
    .task
    修饰符(而非
    onAppear { Task { } }
  • 视图模型和UI状态类标记
    @MainActor
  • 未混合使用
    DispatchSemaphore
    与异步代码(存在死锁风险)
  • withCheckedContinuation
    恰好调用一次resume(不能零次或多次)
  • Actor中的挂起点后重新验证状态(可重入性)
  • 若使用
    @unchecked Sendable
    ,需在注释中说明理由
  • Swift 6.2:诊断前检查项目并发设置(语言版本、默认Actor隔离)
  • Swift 6.2:显式后台任务使用
    @concurrent
    而非
    Task.detached
  • SwiftUI:不在
    Sendable
    闭包(visualEffect、onGeometryChange)中访问
    @MainActor
    状态——使用值拷贝

Cross-References

交叉引用

  • swift-networking
    — async/await network patterns
  • swiftui-ui-patterns
    .task
    modifier,
    @MainActor
    ViewModel pattern,
    Sendable
    checks
  • ios-testing
    — testing async code with Swift Testing (
    #expect
    with
    await
    )
  • code-analyzer
    — concurrency safety review section
  • swift-actor-persistence
    — actor-based persistence pattern with file backing (extends the Actor-Isolated Repository pattern above)
  • swift-networking
    —— async/await网络请求模式
  • swiftui-ui-patterns
    ——
    .task
    修饰符、
    @MainActor
    视图模型模式、Sendable检查
  • ios-testing
    —— 使用Swift Testing测试异步代码(
    #expect
    结合
    await
  • code-analyzer
    —— 并发安全评审章节
  • swift-actor-persistence
    —— 基于Actor的文件存储持久化模式(扩展上述Actor隔离仓库模式)