axiom-swift-concurrency
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwift 6 Concurrency Guide
Swift 6 并发指南
Purpose: Progressive journey from single-threaded to concurrent Swift code
Swift Version: Swift 6.0+, Swift 6.2+ for
iOS Version: iOS 17+ (iOS 18.2+ for )
Xcode: Xcode 16+ (Xcode 16.2+ for )
Context: WWDC 2025-268 "Embracing Swift concurrency" - approachable path to data-race safety
@concurrent@concurrent@concurrent用途:从单线程代码到并发 Swift 代码的渐进式演进路径
Swift 版本:Swift 6.0+,使用 需要 Swift 6.2+
iOS 版本:iOS 17+,使用 需要 iOS 18.2+
Xcode:Xcode 16+,使用 需要 Xcode 16.2+
参考背景:WWDC 2025-268《拥抱 Swift 并发》——实现数据竞争安全的渐进式方案
@concurrent@concurrent@concurrentWhen to Use This Skill
何时使用本指南
✅ Use this skill when:
- Starting a new project and deciding concurrency strategy
- Debugging Swift 6 concurrency errors (actor isolation, data races, Sendable warnings)
- Deciding when to introduce async/await vs concurrency
- Implementing classes or async functions
@MainActor - Converting delegate callbacks to async-safe patterns
- Deciding between ,
@MainActor,nonisolated, or actor isolation@concurrent - Resolving "Sending 'self' risks causing data races" errors
- Making types conform to
Sendable - Offloading CPU-intensive work to background threads
- UI feels unresponsive and profiling shows main thread bottleneck
❌ Do NOT use this skill for:
- General Swift syntax (use Swift documentation)
- SwiftUI-specific patterns (use or
axiom-swiftui-debugging)axiom-swiftui-performance - API-specific patterns (use API documentation)
✅ 以下场景适用:
- 启动新项目并确定并发策略时
- 调试 Swift 6 并发错误(actor 隔离、数据竞争、Sendable 警告)时
- 决定何时引入 async/await 或并发机制时
- 实现 类或异步函数时
@MainActor - 将代理回调转换为异步安全模式时
- 在 、
@MainActor、nonisolated或 actor 隔离之间做选择时@concurrent - 解决「Sending 'self' risks causing data races」错误时
- 让类型遵循 协议时
Sendable - 将 CPU 密集型工作卸载到后台线程时
- UI 响应缓慢,且性能分析显示主线程存在瓶颈时
❌ 以下场景不适用:
- 通用 Swift 语法问题(请参考 Swift 官方文档)
- SwiftUI 特定模式问题(请使用 或
axiom-swiftui-debugging)axiom-swiftui-performance - API 特定模式问题(请参考对应 API 文档)
Core Philosophy: Start Single-Threaded
核心原则:从单线程开始
Apple's Guidance (WWDC 2025-268): "Your apps should start by running all of their code on the main thread, and you can get really far with single-threaded code."
苹果官方指导(WWDC 2025-268):「你的应用应从让所有代码运行在主线程开始,单线程代码就能满足很多场景的需求。」
The Progressive Journey
渐进式演进路径
Single-Threaded → Asynchronous → Concurrent → Actors
↓ ↓ ↓ ↓
Start here Hide latency Background Move data
(network) CPU work off mainWhen to advance:
- Stay single-threaded if UI is responsive and operations are fast
- Add async/await when high-latency operations (network, file I/O) block UI
- Add concurrency when CPU-intensive work (image processing, parsing) freezes UI
- Add actors when too much main actor code causes contention
Key insight: Concurrent code is more complex. Only introduce concurrency when profiling shows it's needed.
单线程 → 异步 → 并发 → Actors
↓ ↓ ↓ ↓
从这里开始 隐藏延迟 后台CPU任务 迁移数据
(网络操作) 处理 脱离主线程何时进阶:
- 保持单线程:如果 UI 响应流畅且操作速度快
- 添加 async/await:当高延迟操作(网络、文件 I/O)阻塞 UI 时
- 添加并发:当 CPU 密集型工作(图像处理、解析)导致 UI 冻结时
- 添加 Actors:当过多主线程 actor 代码引发资源竞争时
关键见解:并发代码复杂度更高。只有当性能分析显示确实需要时,再引入并发机制。
Step 1: Single-Threaded Code (Start Here)
步骤1:单线程代码(从这里开始)
All code runs on the main thread by default in Swift 6.
swift
// ✅ Simple, single-threaded
class ImageModel {
var imageCache: [URL: Image] = [:]
func fetchAndDisplayImage(url: URL) throws {
let data = try Data(contentsOf: url) // Reads local file
let image = decodeImage(data)
view.displayImage(image)
}
func decodeImage(_ data: Data) -> Image {
// Decode image data
return Image()
}
}Main Actor Mode (Xcode 26+):
- Enabled by default for new projects
- All code protected by unless explicitly marked otherwise
@MainActor - Access shared state safely without worrying about concurrent access
Build Setting (Xcode 26+):
Build Settings → Swift Compiler — Language
→ "Default Actor Isolation" = Main Actor
Build Settings → Swift Compiler — Upcoming Features
→ "Approachable Concurrency" = YesWhen this is enough: If all operations are fast (<16ms for 60fps), stay single-threaded!
在 Swift 6 中,所有代码默认运行在主线程。
swift
// ✅ 简单的单线程代码
class ImageModel {
var imageCache: [URL: Image] = [:]
func fetchAndDisplayImage(url: URL) throws {
let data = try Data(contentsOf: url) // 读取本地文件
let image = decodeImage(data)
view.displayImage(image)
}
func decodeImage(_ data: Data) -> Image {
// 解码图像数据
return Image()
}
}主线程模式(Xcode 26+):
- 新项目默认启用
- 所有代码受 保护,除非显式标记为其他隔离方式
@MainActor - 可以安全访问共享状态,无需担心并发访问问题
构建设置(Xcode 26+):
构建设置 → Swift 编译器 — 语言
→ "默认 Actor 隔离" = 主线程
构建设置 → Swift 编译器 — 即将推出的功能
→ "渐进式并发" = 是何时足够:如果所有操作都很快(60fps 下耗时 <16ms),保持单线程即可!
Step 2: Asynchronous Tasks (Hide Latency)
步骤2:异步任务(隐藏延迟)
Add async/await when waiting on data (network, file I/O) would freeze UI.
当等待数据(网络、文件 I/O)会冻结 UI 时,添加 async/await。
Problem: Network Access Blocks UI
问题:网络访问阻塞 UI
swift
// ❌ Blocks main thread until network completes
func fetchAndDisplayImage(url: URL) throws {
let (data, _) = try URLSession.shared.data(from: url) // ❌ Freezes UI!
let image = decodeImage(data)
view.displayImage(image)
}swift
// ❌ 阻塞主线程直到网络请求完成
func fetchAndDisplayImage(url: URL) throws {
let (data, _) = try URLSession.shared.data(from: url) // ❌ 冻结 UI!
let image = decodeImage(data)
view.displayImage(image)
}Solution: Async/Await
解决方案:Async/Await
swift
// ✅ Suspends without blocking main thread
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url) // ✅ Suspends here
let image = decodeImage(data) // ✅ Resumes here when data arrives
view.displayImage(image)
}What happens:
- Function starts on main thread
- suspends function without blocking main thread
await - URLSession fetches data on background thread (library handles this)
- Function resumes on main thread when data arrives
- UI stays responsive the entire time
swift
// ✅ 挂起函数但不阻塞主线程
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url) // ✅ 在此处挂起
let image = decodeImage(data) // ✅ 数据到达后在此处恢复执行
view.displayImage(image)
}执行流程:
- 函数在主线程启动
- 挂起函数,但不阻塞主线程
await - URLSession 在后台线程获取数据(由库自动处理)
- 数据到达后,函数在主线程恢复执行
- 整个过程中 UI 始终保持响应
Task Creation
任务创建
Create tasks in response to user events:
swift
class ImageModel {
var url: URL = URL(string: "https://swift.org")!
func onTapEvent() {
Task { // ✅ Create task for user action
do {
try await fetchAndDisplayImage(url: url)
} catch {
displayError(error)
}
}
}
}响应用户事件时创建任务:
swift
class ImageModel {
var url: URL = URL(string: "https://swift.org")!
func onTapEvent() {
Task { // ✅ 为用户操作创建任务
do {
try await fetchAndDisplayImage(url: url)
} catch {
displayError(error)
}
}
}
}Task Interleaving (Important Concept)
任务交错(重要概念)
Multiple async tasks can run on the same thread by taking turns:
Task 1: [Fetch Image] → (suspend) → [Decode] → [Display]
Task 2: [Fetch News] → (suspend) → [Display News]
Main Thread Timeline:
[Fetch Image] → [Fetch News] → [Decode Image] → [Display Image] → [Display News]Benefits:
- Main thread never sits idle
- Tasks make progress as soon as possible
- No concurrency yet—still single-threaded!
When to use tasks:
- High-latency operations (network, file I/O)
- Library APIs handle background work for you (URLSession, FileManager)
- Your own code stays on main thread
多个异步任务可以在同一线程上交替执行:
任务1:[获取图像] → (挂起) → [解码] → [显示]
任务2:[获取新闻] → (挂起) → [显示新闻]
主线程时间线:
[获取图像] → [获取新闻] → [解码图像] → [显示图像] → [显示新闻]优势:
- 主线程不会闲置
- 任务一有可能就会推进执行
- 尚未引入并发——仍然是单线程!
何时使用任务:
- 高延迟操作(网络、文件 I/O)
- 库 API 自动处理后台工作(URLSession、FileManager)
- 你的代码始终运行在主线程
Step 3: Concurrent Code (Background Threads)
步骤3:并发代码(后台线程)
Add concurrency when CPU-intensive work blocks UI.
当CPU 密集型工作阻塞 UI 时,添加并发机制。
Problem: Decoding Blocks UI
问题:解码操作阻塞 UI
Profiling shows takes 200ms, causing UI glitches:
decodeImage()swift
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url)
let image = decodeImage(data) // ❌ 200ms on main thread!
view.displayImage(image)
}性能分析显示 耗时 200ms,导致 UI 卡顿:
decodeImage()swift
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url)
let image = decodeImage(data) // ❌ 在主线程耗时 200ms!
view.displayImage(image)
}Solution 1: @concurrent
Attribute (Swift 6.2+)
@concurrent解决方案1:@concurrent
属性(Swift 6.2+)
@concurrentForces function to always run on background thread:
swift
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url)
let image = await decodeImage(data) // ✅ Runs on background thread
view.displayImage(image)
}
@concurrent
func decodeImage(_ data: Data) async -> Image {
// ✅ Always runs on background thread pool
// Good for: image processing, file I/O, parsing
return Image()
}What does:
@concurrent- Function always switches to background thread pool
- Compiler highlights main actor data access (shows what you need to fix)
- Cannot access properties without
@MainActorawait
Requirements: Swift 6.2, Xcode 16.2+, iOS 18.2+
强制函数始终在后台线程运行:
swift
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url)
let image = await decodeImage(data) // ✅ 在后台线程运行
view.displayImage(image)
}
@concurrent
func decodeImage(_ data: Data) async -> Image {
// ✅ 始终在后台线程池运行
// 适用场景:图像处理、文件 I/O、解析
return Image()
}@concurrent- 函数始终切换到后台线程池
- 编译器会高亮显示主线程 actor 数据访问(提示需要修复的内容)
- 必须通过 才能访问
await属性@MainActor
要求:Swift 6.2、Xcode 16.2+、iOS 18.2+
Solution 2: nonisolated
(Library APIs)
nonisolated解决方案2:nonisolated
(库 API)
nonisolatedIf providing a general-purpose API, use instead:
nonisolatedswift
// ✅ Stays on caller's actor
nonisolated
func decodeImage(_ data: Data) -> Image {
// Runs on whatever actor called it
// Main actor → stays on main actor
// Background → stays on background
return Image()
}When to use :
nonisolated- Library APIs where caller decides where work happens
- Small operations that might be OK on main thread
- General-purpose code used in many contexts
When to use :
@concurrent- Operations that should always run on background (image processing, parsing)
- Performance-critical work that shouldn't block UI
如果是提供通用 API,使用 替代:
nonisolatedswift
// ✅ 保持在调用者的 actor 上
nonisolated
func decodeImage(_ data: Data) -> Image {
// 在调用它的 actor 上运行
// 主线程 actor → 保持在主线程 actor
// 后台线程 → 保持在后台线程
return Image()
}何时使用 :
nonisolated- 库 API,由调用者决定工作执行的位置
- 可能在主线程执行也没问题的小型操作
- 用于多种场景的通用代码
何时使用 :
@concurrent- 应该始终在后台运行的操作(图像处理、解析)
- 性能关键型工作,不能阻塞 UI
Breaking Ties to Main Actor
断开与主线程 Actor 的关联
When you mark a function , compiler shows main actor access:
@concurrentswift
@MainActor
class ImageModel {
var cachedImage: [URL: Image] = [:] // Main actor data
@concurrent
func decodeImage(_ data: Data, at url: URL) async -> Image {
if let image = cachedImage[url] { // ❌ Error: main actor access!
return image
}
// decode...
}
}Strategy 1: Move to caller (keep work synchronous):
swift
func fetchAndDisplayImage(url: URL) async throws {
// ✅ Check cache on main actor BEFORE async work
if let image = cachedImage[url] {
view.displayImage(image)
return
}
let (data, _) = try await URLSession.shared.data(from: url)
let image = await decodeImage(data) // No URL needed now
view.displayImage(image)
}
@concurrent
func decodeImage(_ data: Data) async -> Image {
// ✅ No main actor access needed
return Image()
}Strategy 2: Use await (access main actor asynchronously):
swift
@concurrent
func decodeImage(_ data: Data, at url: URL) async -> Image {
// ✅ Await to access main actor data
if let image = await cachedImage[url] {
return image
}
// decode...
}Strategy 3: Make nonisolated (if doesn't need actor):
swift
nonisolated
func decodeImage(_ data: Data) -> Image {
// ✅ No actor isolation, can call from anywhere
return Image()
}当你标记函数为 时,编译器会显示主线程 actor 访问错误:
@concurrentswift
@MainActor
class ImageModel {
var cachedImage: [URL: Image] = [:] // 主线程 actor 数据
@concurrent
func decodeImage(_ data: Data, at url: URL) async -> Image {
if let image = cachedImage[url] { // ❌ 错误:主线程 actor 访问!
return image
}
// 解码...
}
}策略1:移到调用者处(保持工作同步):
swift
func fetchAndDisplayImage(url: URL) async throws {
// ✅ 在异步工作前,在主线程 actor 上检查缓存
if let image = cachedImage[url] {
view.displayImage(image)
return
}
let (data, _) = try await URLSession.shared.data(from: url)
let image = await decodeImage(data) // 现在不需要 URL 了
view.displayImage(image)
}
@concurrent
func decodeImage(_ data: Data) async -> Image {
// ✅ 无需访问主线程 actor 数据
return Image()
}策略2:使用 await(异步访问主线程 actor):
swift
@concurrent
func decodeImage(_ data: Data, at url: URL) async -> Image {
// ✅ 通过 await 访问主线程 actor 数据
if let image = await cachedImage[url] {
return image
}
// 解码...
}策略3:标记为 nonisolated(如果不需要 actor):
swift
nonisolated
func decodeImage(_ data: Data) -> Image {
// ✅ 无 actor 隔离,可以从任何位置调用
return Image()
}Concurrent Thread Pool
并发线程池
When work runs on background:
Main Thread: [UI] → (suspend) → [UI Update]
↓
Background Pool: [Task A] → [Task B] → [Task A resumes]
Thread 1 Thread 2 Thread 3Key points:
- System manages thread pool size (1-2 threads on Watch, many on Mac)
- Task can resume on different thread than it started
- You never specify which thread—system optimizes automatically
当工作在后台运行时:
主线程: [UI] → (挂起) → [UI 更新]
↓
后台线程池: [任务A] → [任务B] → [任务A 恢复]
线程1 线程2 线程3关键点:
- 系统管理线程池大小(Watch 上 1-2 个线程,Mac 上多个线程)
- 任务恢复时可能在不同的线程上
- 你无需指定具体线程——系统会自动优化
Step 4: Actors (Move Data Off Main Thread)
步骤4:Actors(将数据移出主线程)
Add actors when too much code runs on main actor causing contention.
当过多代码运行在主线程 actor上导致资源竞争时,添加 Actors。
Problem: Main Actor Contention
问题:主线程 Actor 资源竞争
swift
@MainActor
class ImageModel {
var cachedImage: [URL: Image] = [:]
let networkManager: NetworkManager = NetworkManager() // ❌ Also @MainActor
func fetchAndDisplayImage(url: URL) async throws {
// ✅ Background work...
let connection = await networkManager.openConnection(for: url) // ❌ Hops to main!
let data = try await connection.data(from: url)
await networkManager.closeConnection(connection, for: url) // ❌ Hops to main!
let image = await decodeImage(data)
view.displayImage(image)
}
}Issue: Background task keeps hopping to main actor for network manager access.
swift
@MainActor
class ImageModel {
var cachedImage: [URL: Image] = [:]
let networkManager: NetworkManager = NetworkManager() // ❌ 同样是 @MainActor
func fetchAndDisplayImage(url: URL) async throws {
// ✅ 后台工作...
let connection = await networkManager.openConnection(for: url) // ❌ 切换到主线程!
let data = try await connection.data(from: url)
await networkManager.closeConnection(connection, for: url) // ❌ 切换到主线程!
let image = await decodeImage(data)
view.displayImage(image)
}
}问题:后台任务为了访问网络管理器,不断切换到主线程 actor。
Solution: Network Manager Actor
解决方案:网络管理器 Actor
swift
// ✅ Move network state off main actor
actor NetworkManager {
var openConnections: [URL: Connection] = [:]
func openConnection(for url: URL) -> Connection {
if let connection = openConnections[url] {
return connection
}
let connection = Connection()
openConnections[url] = connection
return connection
}
func closeConnection(_ connection: Connection, for url: URL) {
openConnections.removeValue(forKey: url)
}
}
@MainActor
class ImageModel {
let networkManager: NetworkManager = NetworkManager()
func fetchAndDisplayImage(url: URL) async throws {
// ✅ Now runs mostly on background
let connection = await networkManager.openConnection(for: url)
let data = try await connection.data(from: url)
await networkManager.closeConnection(connection, for: url)
let image = await decodeImage(data)
view.displayImage(image)
}
}What changed:
- is now an
NetworkManagerinstead ofactor@MainActor class - Network state isolated in its own actor
- Background code can access network manager without hopping to main actor
- Main thread freed up for UI work
swift
// ✅ 将网络状态移出主线程 actor
actor NetworkManager {
var openConnections: [URL: Connection] = [:]
func openConnection(for url: URL) -> Connection {
if let connection = openConnections[url] {
return connection
}
let connection = Connection()
openConnections[url] = connection
return connection
}
func closeConnection(_ connection: Connection, for url: URL) {
openConnections.removeValue(forKey: url)
}
}
@MainActor
class ImageModel {
let networkManager: NetworkManager = NetworkManager()
func fetchAndDisplayImage(url: URL) async throws {
// ✅ 现在大部分工作在后台运行
let connection = await networkManager.openConnection(for: url)
let data = try await connection.data(from: url)
await networkManager.closeConnection(connection, for: url)
let image = await decodeImage(data)
view.displayImage(image)
}
}变化:
- 现在是
NetworkManager而非actor@MainActor class - 网络状态隔离在独立的 actor 中
- 后台代码访问网络管理器时无需切换到主线程 actor
- 主线程得以释放,专注于 UI 工作
When to Use Actors
何时使用 Actors
✅ Use actors for:
- Non-UI subsystems with independent state (network manager, cache, database)
- Data that's causing main actor contention
- Separating concerns from UI code
❌ Do NOT use actors for:
- UI-facing classes (ViewModels, View Controllers) → Use
@MainActor - Model classes used by UI → Keep or non-Sendable
@MainActor - Every class in your app (actors add complexity)
Guideline: Profile first. If main actor has too much state causing bottlenecks, extract one subsystem at a time into actors.
✅ 适用场景:
- 具有独立状态的非 UI 子系统(网络管理器、缓存、数据库)
- 导致主线程 actor 资源竞争的数据
- 与 UI 代码分离关注点
❌ 不适用场景:
- UI 相关类(ViewModels、视图控制器)→ 使用
@MainActor - UI 所用的模型类 → 保持 或非 Sendable
@MainActor - 应用中的每个类(Actors 会增加复杂度)
指导原则:先做性能分析。如果主线程 actor 因过多状态导致性能下降,再将子系统提取为 actor。
Sendable Types (Data Crossing Actor Boundaries)
Sendable 类型(跨 Actor 边界传递数据)
When data passes between actors or tasks, Swift checks it's Sendable (safe to share).
当数据在 actors 或任务之间传递时,Swift 会检查它是否为Sendable(可安全共享)。
Value Types Are Sendable
值类型是 Sendable
swift
// ✅ Value types copy when passed
let url = URL(string: "https://swift.org")!
Task {
// ✅ This is a COPY of url, not the original
// URLSession.shared.data runs on background automatically
let data = try await URLSession.shared.data(from: url)
}
// ✅ Original url unchanged by background taskWhy safe: Each actor gets its own independent copy. Changes don't affect other copies.
swift
// ✅ 值类型传递时会复制
let url = URL(string: "https://swift.org")!
Task {
// ✅ 这是 url 的副本,而非原对象
// URLSession.shared.data 自动在后台运行
let data = try await URLSession.shared.data(from: url)
}
// ✅ 原 url 不受后台任务影响安全性原因:每个 actor 都会获得独立的副本。修改副本不会影响其他副本。
What's Sendable?
哪些类型是 Sendable?
swift
// ✅ Basic types
extension URL: Sendable {}
extension String: Sendable {}
extension Int: Sendable {}
extension Date: Sendable {}
// ✅ Collections of Sendable elements
extension Array: Sendable where Element: Sendable {}
extension Dictionary: Sendable where Key: Sendable, Value: Sendable {}
// ✅ Structs/enums with Sendable storage
struct Track: Sendable {
let id: String
let title: String
let duration: TimeInterval
}
enum PlaybackState: Sendable {
case stopped
case playing
case paused
}
// ✅ Main actor types
@MainActor class ImageModel {} // Implicitly Sendable (actor protects state)
// ✅ Actor types
actor NetworkManager {} // Implicitly Sendable (actor protects state)swift
// ✅ 基础类型
extension URL: Sendable {}
extension String: Sendable {}
extension Int: Sendable {}
extension Date: Sendable {}
// ✅ 元素为 Sendable 的集合
extension Array: Sendable where Element: Sendable {}
extension Dictionary: Sendable where Key: Sendable, Value: Sendable {}
// ✅ 存储为 Sendable 属性的结构体/枚举
struct Track: Sendable {
let id: String
let title: String
let duration: TimeInterval
}
enum PlaybackState: Sendable {
case stopped
case playing
case paused
}
// ✅ 主线程 actor 类型
@MainActor class ImageModel {} // 隐式 Sendable(actor 保护状态)
// ✅ Actor 类型
actor NetworkManager {} // 隐式 Sendable(actor 保护状态)Reference Types (Classes) and Sendable
引用类型(类)与 Sendable
swift
// ❌ Classes are NOT Sendable by default
class MyImage {
var width: Int
var height: Int
var pixels: [Color]
func scale(by factor: Double) {
// Mutates shared state
}
}
let image = MyImage()
let otherImage = image // ✅ Both reference SAME object
image.scale(by: 0.5) // ✅ Changes visible through otherImage!Problem with concurrency:
swift
func scaleAndDisplay(imageName: String) {
let image = loadImage(imageName)
Task {
image.scale(by: 0.5) // Background task modifying
}
view.displayImage(image) // Main thread reading
// ❌ DATA RACE! Both threads could touch same object!
}Solution 1: Finish modifications before sending:
swift
@concurrent
func scaleAndDisplay(imageName: String) async {
let image = loadImage(imageName)
image.scale(by: 0.5) // ✅ All modifications on background
image.applyAnotherEffect() // ✅ Still on background
await view.displayImage(image) // ✅ Send to main actor AFTER modifications done
// ✅ Main actor now owns image exclusively
}Solution 2: Don't share classes concurrently:
Keep model classes or non-Sendable to prevent concurrent access.
@MainActorswift
// ❌ 类默认不是 Sendable
class MyImage {
var width: Int
var height: Int
var pixels: [Color]
func scale(by factor: Double) {
// 修改共享状态
}
}
let image = MyImage()
let otherImage = image // ✅ 两者引用同一个对象
image.scale(by: 0.5) // ✅ 修改对 otherImage 可见!并发场景下的问题:
swift
func scaleAndDisplay(imageName: String) {
let image = loadImage(imageName)
Task {
image.scale(by: 0.5) // 后台任务修改对象
}
view.displayImage(image) // 主线程读取对象
// ❌ 数据竞争!两个线程可能同时访问同一个对象!
}解决方案1:传递前完成修改:
swift
@concurrent
func scaleAndDisplay(imageName: String) async {
let image = loadImage(imageName)
image.scale(by: 0.5) // ✅ 所有修改在后台完成
image.applyAnotherEffect() // ✅ 仍在后台
await view.displayImage(image) // ✅ 修改完成后再传递到主线程 actor
// ✅ 主线程 actor 现在独占该对象
}解决方案2:不要并发共享类:
将模型类标记为 或非 Sendable,以防止并发访问。
@MainActorSendable Checking
Sendable 检查
Happens automatically when:
- Passing data into/out of actors
- Passing data into/out of tasks
- Crossing actor boundaries with
await
swift
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url)
// ↑ Sendable ↑ Sendable (crosses to background)
let image = await decodeImage(data)
// ↑ data crosses to background (must be Sendable)
// ↑ image returns to main (must be Sendable)
}以下场景会自动进行 Sendable 检查:
- 在 actors 或任务之间传递数据时
- 用 跨 actor 边界时
await
swift
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url)
// ↑ Sendable ↑ Sendable(切换到后台)
let image = await decodeImage(data)
// ↑ data 切换到后台(必须是 Sendable)
// ↑ image 返回主线程(必须是 Sendable)
}Common Patterns (Copy-Paste Templates)
常见模式(可直接复制的模板)
Pattern 1: Sendable Enum/Struct
模式1:Sendable 枚举/结构体
When: Type crosses actor boundaries
swift
// ✅ Enum (no associated values)
private enum PlaybackState: Sendable {
case stopped
case playing
case paused
}
// ✅ Struct (all properties Sendable)
struct Track: Sendable {
let id: String
let title: String
let artist: String?
}
// ✅ Enum with Sendable associated values
enum Result: Sendable {
case success(data: Data)
case failure(error: Error) // Error is Sendable
}适用场景:类型需要跨 actor 边界传递
swift
// ✅ 枚举(无关联值)
private enum PlaybackState: Sendable {
case stopped
case playing
case paused
}
// ✅ 结构体(所有属性都是 Sendable)
struct Track: Sendable {
let id: String
let title: String
let artist: String?
}
// ✅ 关联值为 Sendable 的枚举
enum Result: Sendable {
case success(data: Data)
case failure(error: Error) // Error 是 Sendable
}Pattern 2: Delegate Value Capture (CRITICAL)
模式2:代理值捕获(关键)
When: delegate method needs to update state
nonisolated@MainActorswift
nonisolated func delegate(_ param: SomeType) {
// ✅ Step 1: Capture delegate parameter values BEFORE Task
let value = param.value
let status = param.status
// ✅ Step 2: Task hop to MainActor
Task { @MainActor in
// ✅ Step 3: Safe to access self (we're on MainActor)
self.property = value
print("Status: \(status)")
}
}Why: Delegate methods are (called from library's threads). Capture parameters before Task. Accessing inside is safe.
nonisolatedselfTask { @MainActor in }适用场景: 代理方法需要更新 状态
nonisolated@MainActorswift
nonisolated func delegate(_ param: SomeType) {
// ✅ 步骤1:在创建 Task 前捕获代理参数值
let value = param.value
let status = param.status
// ✅ 步骤2:切换到 MainActor 的 Task
Task { @MainActor in
// ✅ 步骤3:安全访问 self(当前在 MainActor)
self.property = value
print("状态:\(status)")
}
}原因:代理方法是 (从库的线程调用)。在创建 Task 前捕获参数。在 内访问 是安全的。
nonisolatedTask { @MainActor in }selfPattern 3: Weak Self in Tasks
模式3:任务中的弱引用 Self
When: Task is stored as property OR runs for long time
swift
class MusicPlayer {
private var progressTask: Task<Void, Never>?
func startMonitoring() {
progressTask = Task { [weak self] in // ✅ Weak capture
guard let self = self else { return }
while !Task.isCancelled {
await self.updateProgress()
}
}
}
deinit {
progressTask?.cancel()
}
}Note: Short-lived Tasks (not stored) can use strong captures.
适用场景:任务被存储为属性或运行时间较长
swift
class MusicPlayer {
private var progressTask: Task<Void, Never>?
func startMonitoring() {
progressTask = Task { [weak self] in // ✅ 弱引用捕获
guard let self = self else { return }
while !Task.isCancelled {
await self.updateProgress()
}
}
}
deinit {
progressTask?.cancel()
}
}注意:短期任务(未被存储)可以使用强引用捕获。
Pattern 4: Background Work with @concurrent
模式4:使用 @concurrent 处理后台工作
When: CPU-intensive work should always run on background (Swift 6.2+)
swift
@concurrent
func decodeImage(_ data: Data) async -> Image {
// ✅ Always runs on background thread pool
// Good for: image processing, file I/O, JSON parsing
return Image()
}
// Usage
let image = await decodeImage(data) // Automatically offloadsRequirements: Swift 6.2, Xcode 16.2+, iOS 18.2+
适用场景:CPU 密集型工作应始终在后台运行(Swift 6.2+)
swift
@concurrent
func decodeImage(_ data: Data) async -> Image {
// ✅ 始终在后台线程池运行
// 适用场景:图像处理、文件 I/O、JSON 解析
return Image()
}
// 使用方式
let image = await decodeImage(data) // 自动卸载到后台要求:Swift 6.2、Xcode 16.2+、iOS 18.2+
Pattern 5: Isolated Protocol Conformances (Swift 6.2+)
模式5:隔离协议遵循(Swift 6.2+)
When: Type needs to conform to protocol with specific actor isolation
swift
protocol Exportable {
func export()
}
class PhotoProcessor {
@MainActor
func exportAsPNG() {
// Export logic requiring UI access
}
}
// ✅ Conform with explicit isolation
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG() // ✅ Safe: both on MainActor
}
}When to use: Protocol methods need specific actor context (main actor for UI, background for processing)
适用场景:类型需要遵循具有特定 actor 隔离的协议
swift
protocol Exportable {
func export()
}
class PhotoProcessor {
@MainActor
func exportAsPNG() {
// 需要访问 UI 的导出逻辑
}
}
// ✅ 显式指定隔离方式遵循协议
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG() // ✅ 安全:两者都在 MainActor
}
}何时使用:协议方法需要特定的 actor 上下文(UI 相关用主线程,处理任务用后台)
Pattern 6: Atomic Snapshots
模式6:原子快照
When: Reading multiple properties that could change mid-access
swift
var currentTime: TimeInterval {
get async {
// ✅ Cache reference for atomic snapshot
guard let player = player else { return 0 }
return player.currentTime
}
}适用场景:读取可能在访问过程中发生变化的多个属性
swift
var currentTime: TimeInterval {
get async {
// ✅ 缓存引用以实现原子快照
guard let player = player else { return 0 }
return player.currentTime
}
}Pattern 7: MainActor for UI Code
模式7:UI 代码使用 MainActor
When: Code touches UI
swift
@MainActor
class PlayerViewModel: ObservableObject {
@Published var currentTrack: Track?
@Published var isPlaying: Bool = false
func play(_ track: Track) async {
// Already on MainActor
self.currentTrack = track
self.isPlaying = true
}
}适用场景:代码涉及 UI 操作
swift
@MainActor
class PlayerViewModel: ObservableObject {
@Published var currentTrack: Track?
@Published var isPlaying: Bool = false
func play(_ track: Track) async {
// 已在 MainActor
self.currentTrack = track
self.isPlaying = true
}
}Data Persistence Concurrency Patterns
数据持久化并发模式
Pattern 8: Background SwiftData Access
模式8:后台 SwiftData 访问
swift
actor DataFetcher {
let modelContainer: ModelContainer
func fetchAllTracks() async throws -> [Track] {
let context = ModelContext(modelContainer)
let descriptor = FetchDescriptor<Track>(
sortBy: [SortDescriptor(\.title)]
)
return try context.fetch(descriptor)
}
}
@MainActor
class TrackViewModel: ObservableObject {
@Published var tracks: [Track] = []
func loadTracks() async {
let fetchedTracks = try await fetcher.fetchAllTracks()
self.tracks = fetchedTracks // Back on MainActor
}
}swift
actor DataFetcher {
let modelContainer: ModelContainer
func fetchAllTracks() async throws -> [Track] {
let context = ModelContext(modelContainer)
let descriptor = FetchDescriptor<Track>(
sortBy: [SortDescriptor(\.title)]
)
return try context.fetch(descriptor)
}
}
@MainActor
class TrackViewModel: ObservableObject {
@Published var tracks: [Track] = []
func loadTracks() async {
let fetchedTracks = try await fetcher.fetchAllTracks()
self.tracks = fetchedTracks // 返回 MainActor
}
}Pattern 9: Core Data Thread-Safe Fetch
模式9:Core Data 线程安全获取
swift
actor CoreDataFetcher {
func fetchTracksID(genre: String) async throws -> [String] {
let context = persistentContainer.newBackgroundContext()
var trackIDs: [String] = []
try await context.perform {
let request = NSFetchRequest<CDTrack>(entityName: "Track")
request.predicate = NSPredicate(format: "genre = %@", genre)
let results = try context.fetch(request)
trackIDs = results.map { $0.id } // Extract IDs before leaving context
}
return trackIDs // Lightweight, Sendable
}
}swift
actor CoreDataFetcher {
func fetchTracksID(genre: String) async throws -> [String] {
let context = persistentContainer.newBackgroundContext()
var trackIDs: [String] = []
try await context.perform {
let request = NSFetchRequest<CDTrack>(entityName: "Track")
request.predicate = NSPredicate(format: "genre = %@", genre)
let results = try context.fetch(request)
trackIDs = results.map { $0.id } // 离开上下文前提取 ID
}
return trackIDs // 轻量级,Sendable
}
}Pattern 10: Batch Import with Progress
模式10:带进度的批量导入
swift
actor DataImporter {
func importRecords(_ records: [RawRecord], onProgress: @MainActor (Int, Int) -> Void) async throws {
let chunkSize = 1000
let context = ModelContext(modelContainer)
for (index, chunk) in records.chunked(into: chunkSize).enumerated() {
for record in chunk {
context.insert(Track(from: record))
}
try context.save()
let processed = (index + 1) * chunkSize
await onProgress(min(processed, records.count), records.count)
if Task.isCancelled { throw CancellationError() }
}
}
}swift
actor DataImporter {
func importRecords(_ records: [RawRecord], onProgress: @MainActor (Int, Int) -> Void) async throws {
let chunkSize = 1000
let context = ModelContext(modelContainer)
for (index, chunk) in records.chunked(into: chunkSize).enumerated() {
for record in chunk {
context.insert(Track(from: record))
}
try context.save()
let processed = (index + 1) * chunkSize
await onProgress(min(processed, records.count), records.count)
if Task.isCancelled { throw CancellationError() }
}
}
}Pattern 11: GRDB Background Execution
模式11:GRDB 后台执行
swift
actor DatabaseQueryExecutor {
let dbQueue: DatabaseQueue
func fetchUserWithPosts(userId: String) async throws -> (user: User, posts: [Post]) {
return try await dbQueue.read { db in
let user = try User.filter(Column("id") == userId).fetchOne(db)!
let posts = try Post
.filter(Column("userId") == userId)
.order(Column("createdAt").desc)
.limit(100)
.fetchAll(db)
return (user, posts)
}
}
}swift
actor DatabaseQueryExecutor {
let dbQueue: DatabaseQueue
func fetchUserWithPosts(userId: String) async throws -> (user: User, posts: [Post]) {
return try await dbQueue.read { db in
let user = try User.filter(Column("id") == userId).fetchOne(db)!
let posts = try Post
.filter(Column("userId") == userId)
.order(Column("createdAt").desc)
.limit(100)
.fetchAll(db)
return (user, posts)
}
}
}Quick Decision Tree
快速决策树
Starting new feature?
└─ Is UI responsive with all operations on main thread?
├─ YES → Stay single-threaded (Step 1)
└─ NO → Continue...
└─ Do you have high-latency operations? (network, file I/O)
├─ YES → Add async/await (Step 2)
└─ NO → Continue...
└─ Do you have CPU-intensive work? (Instruments shows main thread busy)
├─ YES → Add @concurrent or nonisolated (Step 3)
└─ NO → Continue...
└─ Is main actor contention causing slowdowns?
└─ YES → Extract subsystem to actor (Step 4)
Error: "Main actor-isolated property accessed from nonisolated context"
├─ In delegate method?
│ └─ Pattern 2: Value Capture Before Task
├─ In async function?
│ └─ Add @MainActor or call from Task { @MainActor in }
└─ In @concurrent function?
└─ Move access to caller, use await, or make nonisolated
Error: "Type does not conform to Sendable"
├─ Enum/struct with Sendable properties?
│ └─ Add `: Sendable`
└─ Class?
└─ Make @MainActor or keep non-Sendable (don't share concurrently)
Want to offload work to background?
├─ Always background (image processing)?
│ └─ Use @concurrent (Swift 6.2+)
├─ Caller decides?
│ └─ Use nonisolated
└─ Too much main actor state?
└─ Extract to actor启动新功能?
└─ 所有操作在主线程时 UI 是否响应流畅?
├─ 是 → 保持单线程(步骤1)
└─ 否 → 继续...
└─ 是否存在高延迟操作?(网络、文件 I/O)
├─ 是 → 添加 async/await(步骤2)
└─ 否 → 继续...
└─ 是否存在 CPU 密集型工作?(Instruments 显示主线程繁忙)
├─ 是 → 添加 @concurrent 或 nonisolated(步骤3)
└─ 否 → 继续...
└─ 主线程 Actor 资源竞争是否导致性能下降?
└─ 是 → 将子系统提取为 Actor(步骤4)
错误:"Main actor-isolated property accessed from nonisolated context"
├─ 是否在代理方法中?
│ └─ 使用模式2:创建 Task 前捕获值
├─ 是否在异步函数中?
│ └─ 添加 @MainActor 或在 Task { @MainActor in } 中调用
└─ 是否在 @concurrent 函数中?
└─ 将访问逻辑移到调用者处、使用 await 或标记为 nonisolated
错误:"Type does not conform to Sendable"
├─ 是否是属性均为 Sendable 的枚举/结构体?
│ └─ 添加 `: Sendable`
└─ 是否是类?
└─ 标记为 @MainActor 或保持非 Sendable(不要并发共享)
想要将工作卸载到后台?
├─ 是否始终要在后台运行?(如图像处理)
│ └─ 使用 @concurrent(Swift 6.2+)
├─ 是否由调用者决定运行位置?
│ └─ 使用 nonisolated
└─ 主线程 Actor 状态过多?
└─ 提取为 ActorBuild Settings (Xcode 16+)
构建设置(Xcode 16+)
Build Settings → Swift Compiler — Language
→ "Default Actor Isolation" = Main Actor
→ "Approachable Concurrency" = Yes
Build Settings → Swift Compiler — Concurrency
→ "Strict Concurrency Checking" = CompleteWhat this enables:
- Main actor mode (all code @MainActor by default)
- Compile-time data race prevention
- Progressive concurrency adoption
构建设置 → Swift 编译器 — 语言
→ "默认 Actor 隔离" = 主线程
→ "渐进式并发" = 是
构建设置 → Swift 编译器 — 并发
→ "严格并发检查" = 完整启用的功能:
- 主线程模式(所有代码默认 @MainActor)
- 编译时数据竞争预防
- 渐进式并发采用
Anti-Patterns (DO NOT DO THIS)
反模式(切勿这样做)
❌ Using Concurrency When Not Needed
❌ 无需并发时使用并发
swift
// ❌ Premature optimization
@concurrent
func addNumbers(_ a: Int, _ b: Int) async -> Int {
return a + b // ❌ Trivial work, concurrency adds overhead
}
// ✅ Keep simple
func addNumbers(_ a: Int, _ b: Int) -> Int {
return a + b
}swift
// ❌ 过早优化
@concurrent
func addNumbers(_ a: Int, _ b: Int) async -> Int {
return a + b // ❌ 琐碎工作,并发会增加开销
}
// ✅ 保持简单
func addNumbers(_ a: Int, _ b: Int) -> Int {
return a + b
}❌ Strong Self in Stored Tasks
❌ 存储的任务中使用强引用 Self
swift
// ❌ Memory leak
progressTask = Task {
while true {
await self.update() // ❌ Strong capture
}
}
// ✅ Weak capture
progressTask = Task { [weak self] in
guard let self = self else { return }
// ...
}swift
// ❌ 内存泄漏
progressTask = Task {
while true {
await self.update() // ❌ 强引用
}
}
// ✅ 弱引用
progressTask = Task { [weak self] in
guard let self = self else { return }
// ...
}❌ Making Every Class an Actor
❌ 让每个类都成为 Actor
swift
// ❌ Don't do this
actor MyViewModel: ObservableObject { // ❌ UI code should be @MainActor!
@Published var state: State // ❌ Won't work correctly
}
// ✅ Do this
@MainActor
class MyViewModel: ObservableObject {
@Published var state: State
}swift
// ❌ 切勿这样做
actor MyViewModel: ObservableObject { // ❌ UI 代码应使用 @MainActor!
@Published var state: State // ❌ 无法正常工作
}
// ✅ 正确做法
@MainActor
class MyViewModel: ObservableObject {
@Published var state: State
}Code Review Checklist
代码审查清单
Before Adding Concurrency
添加并发前
- Profiled and confirmed UI unresponsiveness
- Identified specific slow operations (network, CPU, contention)
- Started with simplest solution (async → concurrent → actors)
- 已做性能分析并确认 UI 响应缓慢
- 已识别出具体的缓慢操作(网络、CPU、资源竞争)
- 从最简单的解决方案开始(异步 → 并发 → Actors)
Async/Await
Async/Await
- Used for high-latency operations only
- Task creation in response to events
- Error handling with do-catch
- 仅用于高延迟操作
- 响应用户事件创建任务
- 使用 do-catch 处理错误
Background Work
后台工作
- for always-background work (Swift 6.2+)
@concurrent - for library APIs
nonisolated - No blocking operations on main actor
- 始终在后台运行的工作使用 (Swift 6.2+)
@concurrent - 库 API 使用
nonisolated - 主线程 Actor 上无阻塞操作
Sendable
Sendable
- Value types for data crossing actors
- Classes stay @MainActor or non-Sendable
- No concurrent modification of shared classes
- 跨 Actors 传递的数据使用值类型
- 类保持 @MainActor 或非 Sendable
- 无共享类的并发修改
Actors
Actors
- Only for non-UI subsystems
- UI code stays @MainActor
- Model classes stay @MainActor or non-Sendable
- 仅用于非 UI 子系统
- UI 代码保持 @MainActor
- 模型类保持 @MainActor 或非 Sendable
Real-World Impact
实际影响
Before: Random crashes, data races, "works on my machine" bugs, premature complexity
After: Compile-time guarantees, progressive adoption, only use concurrency when needed
Key insight: Swift 6's approach makes you prove code is safe before compilation succeeds. Start simple, add complexity only when profiling proves it's needed.
之前:随机崩溃、数据竞争、「在我机器上正常」的 bug、过早的复杂度
之后:编译时安全保障、渐进式采用、仅在需要时使用并发
关键见解:Swift 6 的方案要求你在编译通过前证明代码是安全的。从简单开始,只有当性能分析证明需要时,再引入复杂度。
Resources
参考资源
WWDC: 2025-268, 2025-245, 2022-110351, 2021-10133
Docs: /swift/adoptingswift6, /swift/sendable
Last Updated: 2025-12-01
Status: Enhanced with WWDC 2025-268 progressive journey, @concurrent attribute, isolated conformances, and approachable concurrency patterns
WWDC:2025-268, 2025-245, 2022-110351, 2021-10133
文档:/swift/adoptingswift6, /swift/sendable
最后更新:2025-12-01
状态:结合 WWDC 2025-268 渐进式路径、@concurrent 属性、隔离遵循和渐进式并发模式进行了增强