swift-concurrency
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwift Concurrency
Swift 并发编程
Lifecycle Position
生命周期定位
Phase 3 (Implement) and Phase 5 (Review). Load for async patterns during implementation; use checklist during code review. Related: for async network calls.
swift-networking第3阶段(实现)和第5阶段(评审)。在实现阶段学习异步模式;在代码评审阶段使用检查清单。相关技能:(用于异步网络请求)。
swift-networkingDecision 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 requiredswift
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 requiredActor 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 inside an actor.
awaitswift
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内部的任何操作后,必须重新验证状态。
awaitSwiftData Concurrency
SwiftData 并发
@Model- is non-Sendable — do not pass model instances across actor boundaries
@Model - is
ModelContext-bound — create and use on the main actor@MainActor - is Sendable — safe to pass between actors
ModelContainer
@Model- 不遵循Sendable — 不要跨Actor边界传递模型实例
@Model - 绑定到
ModelContext— 在主Actor上创建和使用@MainActor - 遵循Sendable — 可安全在Actor之间传递
ModelContainer
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? Itemswift
// 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? ItemTesting Implications
测试注意事项
When writing tests for models that use SwiftData:
@Observable @MainActor- Test suites must be (matches the model's isolation)
@MainActor - Use on
.serializedto prevent parallel container conflicts@Suite - See skill for complete SwiftData testing patterns
ios-testing
为使用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)
sendingsending
关键字(Swift 6)
sendingswift
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 parallelswift
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 parallelTaskGroup (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:
- — 90% of cases. Starting async work from sync context.
Task { } - — CPU-heavy work that must NOT run on MainActor.
Task.detached { }
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) }
}使用场景:
- — 90%的场景,从同步上下文启动异步任务。
Task { } - — 必须不在主Actor上运行的CPU密集型任务。
Task.detached { }
Migration from GCD
从GCD迁移
| GCD Pattern | Modern Equivalent |
|---|---|
| |
| |
| |
| Actor isolation (semaphores + async = deadlock risk) |
| Actor |
| Completion handler | |
| GCD 模式 | 现代替代方案 |
|---|---|
| |
| |
| |
| Actor隔离(信号量+异步=死锁风险) |
| Actor |
| 完成回调 | |
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 SomeOldLibraryswift
// Enable per-target in Package.swift
.target(name: "MyApp", swiftSettings: [.swiftLanguageMode(.v6)])
// Or per-file: suppress specific warnings during migration
@preconcurrency import SomeOldLibraryCommon Warnings and Fixes
常见警告与修复方案
| Warning | Fix |
|---|---|
| "Sending 'value' risks data race" | Make type |
| "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 |
| "Global variable is not concurrency-safe" | Make it |
| "Conformance of X to protocol Y crosses into main actor-isolated code" | Use isolated conformance: |
| 警告 | 修复方法 |
|---|---|
| "Sending 'value' risks data race" | 让类型遵循 |
| "Non-sendable type captured by @Sendable closure" | 移至Actor隔离方法或让类型遵循Sendable |
| "Actor-isolated property cannot be mutated from nonisolated context" | 添加 |
| "Global variable is not concurrency-safe" | 设为 |
| "Conformance of X to protocol Y crosses into main actor-isolated code" | 使用隔离协议一致性: |
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 for full details.
references/swift-6-2-changes.md| Feature | What It Does |
|---|---|
| Async stays on caller's actor | No implicit background hop — eliminates "Sending X risks data races" errors |
| Explicit opt-in to run on concurrent pool (replaces |
| Main-actor-by-default mode | Opt-in: all types in a target implicitly |
| Isolated conformances | |
SwiftUI-specific: See for off-main-thread APIs (, , , ) and their closure requirements.
references/swiftui-concurrency.mdShapeLayoutvisualEffectonGeometryChangeSendableProject 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"错误 |
| 显式选择加入并发池运行(替代此场景下的 |
| 默认主Actor模式 | 可选启用:目标中的所有类型隐式标记为 |
| 隔离协议一致性 | |
SwiftUI特定内容: 非主线程API(、、、)及其闭包要求请参考。
ShapeLayoutvisualEffectonGeometryChangeSendablereferences/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() - references stored for cleanup if not using
Taskmodifier.task - modifier used for view lifecycle async work (not
.task)onAppear { Task { } } - on view models and UI state classes
@MainActor - No mixed with async code (deadlock risk)
DispatchSemaphore - resumes exactly once (not zero, not twice)
withCheckedContinuation - State re-validated after suspension points in actors (reentrancy)
- justified in comment if used
@unchecked Sendable - Swift 6.2: Check project concurrency settings before diagnosing (language version, default actor isolation)
- Swift 6.2: used instead of
@concurrentfor explicit background workTask.detached - SwiftUI: No state accessed from
@MainActorclosures (visualEffect, onGeometryChange) — use value copiesSendable
- 无数据竞争:可变共享状态在Actor中或受隔离保护
- Actor隔离边界清晰文档化(哪个Actor拥有哪个状态)
- 协作式取消:长时操作检查
Task.checkCancellation() - 若未使用修饰符,需存储
.task引用以进行清理Task - 视图生命周期异步任务使用修饰符(而非
.task)onAppear { Task { } } - 视图模型和UI状态类标记
@MainActor - 未混合使用与异步代码(存在死锁风险)
DispatchSemaphore - 恰好调用一次resume(不能零次或多次)
withCheckedContinuation - Actor中的挂起点后重新验证状态(可重入性)
- 若使用,需在注释中说明理由
@unchecked Sendable - Swift 6.2:诊断前检查项目并发设置(语言版本、默认Actor隔离)
- Swift 6.2:显式后台任务使用而非
@concurrentTask.detached - SwiftUI:不在闭包(visualEffect、onGeometryChange)中访问
Sendable状态——使用值拷贝@MainActor
Cross-References
交叉引用
- — async/await network patterns
swift-networking - —
swiftui-ui-patternsmodifier,.taskViewModel pattern,@MainActorchecksSendable - — testing async code with Swift Testing (
ios-testingwith#expect)await - — concurrency safety review section
code-analyzer - — actor-based persistence pattern with file backing (extends the Actor-Isolated Repository pattern above)
swift-actor-persistence
- —— async/await网络请求模式
swift-networking - ——
swiftui-ui-patterns修饰符、.task视图模型模式、Sendable检查@MainActor - —— 使用Swift Testing测试异步代码(
ios-testing结合#expect)await - —— 并发安全评审章节
code-analyzer - —— 基于Actor的文件存储持久化模式(扩展上述Actor隔离仓库模式)
swift-actor-persistence