axiom-swift-concurrency-ref
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwift Concurrency API Reference
Swift并发API参考
Complete Swift concurrency API reference for copy-paste patterns and syntax lookup.
Complements (which covers when and why to use concurrency — progressive journey, decision trees, @concurrent, isolated conformances).
axiom-swift-concurrencyRelated skills: (progressive journey, decision trees), (Mutex, locks), (assumeIsolated patterns)
axiom-swift-concurrencyaxiom-synchronizationaxiom-assume-isolated这是一份完整的Swift并发API参考,提供可直接复制粘贴的代码模式和语法查询。
作为的补充内容(后者涵盖了何时以及为何使用并发——循序渐进的指南、决策树、@concurrent、隔离式协议一致性)。
axiom-swift-concurrency相关技能:(循序渐进指南、决策树)、(Mutex、锁)、(assumeIsolated模式)
axiom-swift-concurrencyaxiom-synchronizationaxiom-assume-isolatedPart 1: Actor Patterns
第一部分:Actor模式
Actor Definition
Actor定义
swift
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 to access them.
awaitswift
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
}
}
// 使用方式——跨隔离边界必须使用await
let cache = ImageCache()
let image = await cache.image(for: url)Actor的所有属性和方法默认都是隔离的。Actor隔离域外部的调用者必须使用来访问它们。
awaitActor Isolation Rules
Actor隔离规则
Every actor's stored properties and methods are isolated to that actor. Access from outside the isolation boundary requires , which suspends the caller until the actor can process the request.
awaitswift
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每个Actor的存储属性和方法都与该Actor隔离。从隔离边界外部访问需要使用,这会暂停调用者,直到Actor可以处理该请求。
awaitswift
actor Counter {
var count = 0 // 隔离属性——外部访问需要await
let name: String // let常量默认是非隔离的
func increment() { // 隔离方法——外部调用需要await
count += 1
}
nonisolated func identity() -> String {
name // 允许:访问非隔离的let属性
}
}
let counter = Counter(name: "main")
await counter.increment() // 跨隔离边界必须使用await
let id = counter.identity() // 无需await——非隔离方法nonisolated Keyword
nonisolated关键字
Opt out of isolation for synchronous access to non-mutable state.
swift
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
}
}nonisolatedHashableCustomStringConvertible退出隔离状态,以同步方式访问不可变状态。
swift
actor MyActor {
let id: UUID // let常量默认是非隔离的
nonisolated var description: String {
"Actor \(id)" // 只能访问非隔离状态
}
nonisolated func hash(into hasher: inout Hasher) {
hasher.combine(id) // 仅能访问非隔离属性
}
}nonisolatedHashableCustomStringConvertibleActor Reentrancy
Actor可重入性
Suspension points () inside an actor allow other callers to interleave. State may change between any two expressions.
awaitawaitswift
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 inside an actor:
awaitswift
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
}
}Actor内部的挂起点()允许其他调用者插入执行。在任意两个表达式之间,状态可能会发生变化。
awaitawaitswift
actor BankAccount {
var balance: Double = 0
func transfer(amount: Double, to other: BankAccount) async {
guard balance >= amount else { return }
balance -= amount
// 可重入风险:在等待另一个Actor的存款操作时,其他调用者可能会修改balance
await other.deposit(amount)
}
func deposit(_ amount: Double) {
balance += amount
}
}解决方案:在Actor内部的每个之后重新检查状态:
awaitswift
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)
// 若有需要,在await后重新检查不变量
return true
}
}Global Actors
全局Actor
A global actor provides a single shared isolation domain accessible from anywhere.
swift
@globalActor
actor MyGlobalActor {
static let shared = MyGlobalActor()
}
@MyGlobalActor
func doWork() { /* isolated to MyGlobalActor */ }
@MyGlobalActor
class MyService {
var state: Int = 0 // Isolated to MyGlobalActor
}全局Actor提供一个单一的共享隔离域,可从任何位置访问。
swift
@globalActor
actor MyGlobalActor {
static let shared = MyGlobalActor()
}
@MyGlobalActor
func doWork() { /* 隔离到MyGlobalActor */ }
@MyGlobalActor
class MyService {
var state: Int = 0 // 隔离到MyGlobalActor
}@MainActor
@MainActor
The built-in global actor for UI work. All UI updates must happen on .
@MainActorswift
@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 , all subclasses inherit that isolation.
@MainActor用于UI工作的内置全局Actor。所有UI更新都必须在上执行。
@MainActorswift
@MainActor
class ViewModel: ObservableObject {
@Published var items: [Item] = []
func loadItems() async {
let data = await fetchFromNetwork()
items = data // 安全:已处于MainActor上下文
}
}
// 为单个成员添加注解
class MixedService {
@MainActor var uiState: String = ""
@MainActor
func updateUI() {
uiState = "Done"
}
func backgroundWork() async -> String {
await heavyComputation()
}
}子类继承:如果一个类标记为,所有子类都会继承该隔离属性。
@MainActorActor Init
Actor初始化
Actor initializers are NOT isolated to the actor. You cannot call isolated methods from init.
swift
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的初始化方法不与Actor隔离。你无法在init中调用隔离方法。
swift
actor DataManager {
var data: [String] = []
init() {
// 此处无法调用隔离方法
// self.loadDefaults() // 错误:在非隔离的init中调用Actor隔离方法
}
// 改用工厂方法
static func create() async -> DataManager {
let manager = DataManager()
await manager.loadDefaults()
return manager
}
func loadDefaults() {
data = ["default"]
}
}Actor Gotcha Table
Actor问题排查表
| Gotcha | Symptom | Fix |
|---|---|---|
| Actor reentrancy | State changes between awaits | Re-check state after each await |
| nonisolated accessing isolated state | Compiler error | Remove nonisolated or make property nonisolated |
| Calling actor method from sync context | "Expression is 'async'" | Wrap in Task {} or make caller async |
| Global actor inheritance | Subclass inherits @MainActor | Be intentional about which methods need isolation |
| Actor init not isolated | Can't call isolated methods in init | Use factory method or populate after init |
| Actor protocol conformance | "Non-isolated" conformance error | Use nonisolated for protocol methods, or isolated conformance (Swift 6.2+) |
| 问题 | 症状 | 修复方案 |
|---|---|---|
| Actor可重入 | await之间状态发生变化 | 每个await后重新检查状态 |
| nonisolated方法访问隔离状态 | 编译器错误 | 移除nonisolated标记,或将属性改为非隔离 |
| 从同步上下文调用Actor方法 | "Expression is 'async'"错误 | 用Task {}包裹,或让调用者改为async |
| 全局Actor继承 | 子类继承@MainActor | 明确哪些方法需要隔离 |
| Actor init不隔离 | 无法在init中调用隔离方法 | 使用工厂方法,或在init后填充数据 |
| Actor协议一致性 | "Non-isolated"一致性错误 | 为协议方法使用nonisolated,或使用隔离式一致性(Swift 6.2+) |
Part 2: Sendable Patterns
第二部分:Sendable模式
Automatic Sendable Conformance
自动Sendable一致性
Value types are Sendable when all stored properties are Sendable.
swift
// 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时,值类型自动符合Sendable。
swift
// 结构体:所有存储属性为Sendable时,结构体自动符合Sendable
struct UserProfile: Sendable {
let name: String
let age: Int
}
// 枚举:所有关联值为Sendable时,枚举自动符合Sendable
enum LoadState: Sendable {
case idle
case loading
case loaded(String) // String是Sendable
case failed(Error) // 错误:Error不符合Sendable
}
// 修复方案:使用符合Sendable的错误类型
enum LoadState: Sendable {
case idle
case loading
case loaded(String)
case failed(any Error & Sendable)
}@Sendable Closures
@Sendable闭包
Closures passed across isolation boundaries must be . A closure cannot capture mutable local state.
@Sendable@Sendableswift
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
}跨隔离边界传递的闭包必须标记为。闭包无法捕获可变的本地状态。
@Sendable@Sendableswift
func runInBackground(_ work: @Sendable () -> Void) {
Task.detached { work() }
}
// 所有捕获的值必须是Sendable
var count = 0
runInBackground {
// 错误:捕获可变本地变量
// count += 1
}
let snapshot = count
runInBackground {
print(snapshot) // 允许:捕获Sendable类型的let绑定
}@unchecked Sendable
@unchecked Sendable
Manual guarantee of thread safety. Use only when you provide synchronization yourself.
swift
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
手动保证线程安全。仅当你自己提供同步机制时使用。
swift
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
}
}@unchecked Sendable的要求:
- 类必须是
final - 所有可变状态必须由同步原语(锁、队列、Mutex)保护
- 你需要对正确性负责——编译器不会进行检查
Conditional Conformance
条件一致性
swift
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: Sendableswift
struct Box<T> {
let value: T
}
// 仅当T是Sendable时,Box才符合Sendable
extension Box: Sendable where T: Sendable {}
// 标准库广泛使用此模式:
// Array<Element>: Sendable where Element: Sendable
// Dictionary<Key, Value>: Sendable where Key: Sendable, Value: Sendable
// Optional<Wrapped>: Sendable where Wrapped: Sendablesending Parameter Modifier (SE-0430)
sending参数修饰符(SE-0430)
Transfer ownership of a value across isolation boundaries. The caller gives up access.
swift
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)
}跨隔离边界转移值的所有权。调用者将失去对该值的访问权限。
swift
func process(_ value: sending String) async {
// 调用者在此调用后无法再访问value
await store(value)
}
// 当调用者不再需要使用非Sendable类型时,可用于转移所有权
func handOff(_ connection: sending NetworkConnection) async {
await manager.accept(connection)
}Build Settings
构建设置
Control the strictness of Sendable checking in Xcode:
| Setting | Value | Behavior |
|---|---|---|
| | Only explicit Sendable annotations checked |
| | Inferred Sendable + closure checking |
| | Full strict concurrency (Swift 6 default) |
在Xcode中控制Sendable检查的严格程度:
| 设置项 | 值 | 行为 |
|---|---|---|
| | 仅检查显式的Sendable注解 |
| | 检查推断的Sendable和闭包 |
| | 完全严格的并发检查(Swift 6默认) |
Sendable Gotcha Table
Sendable问题排查表
| Gotcha | Symptom | Fix |
|---|---|---|
| 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 Sendable | Generic constraints complex | Use |
| @unchecked Sendable hides bugs | Data races at runtime | Only use when lock/queue guarantees safety |
| Array/Dictionary conditional | Collection is Sendable only if Element is | Ensure element types are Sendable |
| Error not Sendable | "Type does not conform to Sendable" | Use |
| 问题 | 症状 | 修复方案 |
|---|---|---|
| 类无法符合Sendable | "Class cannot conform to Sendable"错误 | 将类设为final且不可变,或使用带锁的@unchecked Sendable |
| 闭包捕获非Sendable类型 | "Capture of non-Sendable type"错误 | 捕获前复制值,或让类型符合Sendable |
| 协议无法要求Sendable | 泛型约束复杂 | 使用 |
| @unchecked Sendable隐藏bug | 运行时出现数据竞争 | 仅当锁/队列能保证安全时使用 |
| 数组/字典的条件一致性 | 集合仅当元素为Sendable时才符合Sendable | 确保元素类型符合Sendable |
| Error不符合Sendable | "Type does not conform to Sendable"错误 | 使用 |
Part 3: Task Management
第三部分:任务管理
Task { }
Task { }
Creates an unstructured task that inherits the current actor context and priority.
swift
// 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创建一个非结构化任务,继承当前的Actor上下文和优先级。
swift
// 继承Actor上下文——如果从@MainActor调用,则在MainActor上运行
let task = Task {
try await fetchData()
}
// 获取结果
let result = try await task.value
// 获取Result<Success, Failure>
let outcome = await task.resultTask.detached { }
Task.detached { }
Creates a task with no inherited context. Does not inherit the actor or priority.
swift
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 in most cases — is rarely needed.
Task {}Task.detached创建一个不继承任何上下文的任务。不继承Actor或优先级。
swift
Task.detached(priority: .background) {
// 即使从MainActor创建,也不会在MainActor上运行
await processLargeFile()
}使用场景:必须在调用者Actor之外运行的后台工作。大多数情况下优先使用——很少需要。
Task {}Task.detachedTask Cancellation
任务取消
Cancellation is cooperative. Setting cancellation is a request; the task must check and respond.
swift
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()取消是协作式的。设置取消只是一个请求;任务必须自行检查并响应。
swift
let task = Task {
for item in largeCollection {
// 方式1:检查布尔值
if Task.isCancelled { break }
// 方式2:抛出CancellationError
try Task.checkCancellation()
await process(item)
}
}
// 请求取消
task.cancel()Task.sleep
Task.sleep
Suspends the current task for a duration. Supports cancellation — throws if cancelled during sleep.
CancellationErrorswift
// 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)暂停当前任务一段时长。支持取消——如果在睡眠期间被取消,会抛出。
CancellationErrorswift
// 基于时长的调用(推荐)
try await Task.sleep(for: .seconds(2))
try await Task.sleep(for: .milliseconds(500))
// 纳秒级调用(旧API)
try await Task.sleep(nanoseconds: 2_000_000_000)Task.yield
Task.yield
Voluntarily yields execution to allow other tasks to run. Use in long-running synchronous loops.
swift
for i in 0..<1_000_000 {
if i.isMultiple(of: 1000) {
await Task.yield()
}
process(i)
}主动让出执行权,允许其他任务运行。在长时间运行的同步循环中使用。
swift
for i in 0..<1_000_000 {
if i.isMultiple(of: 1000) {
await Task.yield()
}
process(i)
}Task Priority
任务优先级
| Priority | Use Case |
|---|---|
| Direct user action, visible result |
| Same as .userInitiated |
| Default when not specified |
| Prefetching, non-urgent work |
| Long computation, progress shown |
| Maintenance, cleanup, not time-sensitive |
swift
Task(priority: .userInitiated) {
await loadVisibleContent()
}
Task(priority: .background) {
await cleanupTempFiles()
}| 优先级 | 使用场景 |
|---|---|
| 用户直接操作,结果可见 |
| 与.userInitiated相同 |
| 未指定时的默认优先级 |
| 预取、非紧急工作 |
| 长时间计算,显示进度 |
| 维护、清理、非时间敏感工作 |
swift
Task(priority: .userInitiated) {
await loadVisibleContent()
}
Task(priority: .background) {
await cleanupTempFiles()
}@TaskLocal
@TaskLocal
Task-scoped values that propagate to child tasks automatically.
swift
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) // nilPropagation rules: values propagate to child tasks created with . They do NOT propagate to .
@TaskLocalTask {}Task.detached {}任务作用域内的值,会自动传播到子任务。
swift
enum RequestContext {
@TaskLocal static var requestID: String?
@TaskLocal static var userID: String?
}
// 在作用域内设置值
RequestContext.$requestID.withValue("req-123") {
RequestContext.$userID.withValue("user-456") {
// 此处及子任务中均可访问这两个值
Task {
print(RequestContext.requestID) // "req-123"
print(RequestContext.userID) // "user-456"
}
}
}
// 作用域外部——值为nil
print(RequestContext.requestID) // nil传播规则:值会传播给用创建的子任务。不会传播给创建的任务。
@TaskLocalTask {}Task.detached {}Task Gotcha Table
任务问题排查表
| Gotcha | Symptom | Fix |
|---|---|---|
| Task never cancelled | Resource leak, work continues after view disappears | Store task, cancel in deinit/onDisappear |
| Ignoring cancellation | Task runs to completion even when cancelled | Check 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 Task | Potential retain cycle | Use [weak self] for long-lived tasks |
| TaskLocal not propagated | Value is nil in detached task | TaskLocal only propagates to child tasks, not detached |
| Task priority inversion | Low-priority task blocks high-priority | System handles most cases; avoid awaiting low-priority from high |
| 问题 | 症状 | 修复方案 |
|---|---|---|
| 任务从未被取消 | 资源泄漏,视图消失后工作仍在继续 | 保存任务引用,在deinit/onDisappear中取消 |
| 忽略取消请求 | 即使被取消,任务仍运行到完成 | 在循环中检查Task.isCancelled,使用checkCancellation() |
| Task.detached丢失Actor上下文 | "Not isolated to MainActor"错误 | 需要Actor隔离时使用Task {} |
| 在Task中捕获self | 潜在的循环引用 | 对长生命周期任务使用[weak self] |
| TaskLocal未传播 | 分离任务中的值为nil | TaskLocal仅传播给子任务,不传播给分离任务 |
| 任务优先级反转 | 低优先级任务阻塞高优先级任务 | 系统会处理大多数情况;避免在高优先级任务中等待低优先级任务 |
Part 4: Structured Concurrency
第四部分:结构化并发
async let
async let
Run a fixed number of operations in parallel. All bindings are implicitly awaited when the scope exits.
async letswift
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 throws, the others are cancelled. All must complete (or be cancelled) before the enclosing scope exits.
async let并行运行固定数量的操作。当作用域退出时,所有绑定会被隐式等待。
async letswift
async let images = fetchImages()
async let metadata = fetchMetadata()
async let config = loadConfig()
// 三个操作并发运行,一起等待结果
let (imgs, meta, cfg) = try await (images, metadata, config)语义:如果一个抛出错误,其他操作会被自动取消。所有操作必须完成(或被取消)后,封闭作用域才会退出。
async letTaskGroup — Non-Throwing
TaskGroup — 非抛出型
Dynamic number of parallel tasks where none throw.
swift
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
}动态数量的并行任务,且不会抛出错误。
swift
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
TaskGroup — 抛出型
Dynamic number of parallel tasks that can throw.
swift
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
}动态数量的并行任务,可能会抛出错误。
swift
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+)
withDiscardingTaskGroup (iOS 17+)
For when you need concurrency but don't need to collect results.
swift
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
}当你需要并发但不需要收集结果时使用。
swift
try await withThrowingDiscardingTaskGroup { group in
for connection in connections {
group.addTask {
try await connection.monitor()
// 结果会被丢弃——适用于长时间运行的服务
}
}
// 所有任务完成或其中一个抛出错误前,Group会保持活跃
}TaskGroup Control
TaskGroup控制
swift
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)
}
}swift
await withTaskGroup(of: Data.self) { group in
// 有条件地添加任务
group.addTaskUnlessCancelled {
await fetchData()
}
// 取消剩余任务
group.cancelAll()
// 等待所有任务完成但不收集结果
await group.waitForAll()
// 逐个获取结果
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 and TaskGroup children
async let - 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
swift
// If fetchImages() throws, fetchMetadata() is automatically cancelled
async let images = fetchImages()
async let metadata = fetchMetadata()
let result = try await (images, metadata)结构化并发形成一个树状结构:
- 父任务取消会取消所有子任务——取消一个任务会取消所有和TaskGroup子任务
async let - 子任务错误会传播到父任务——在抛出型Group中,子任务错误会取消兄弟任务并向上传播
- 所有子任务必须在父任务返回前完成——作用域会等待所有子任务,即使是已取消的任务
swift
// 如果fetchImages()抛出错误,fetchMetadata()会被自动取消
async let images = fetchImages()
async let metadata = fetchMetadata()
let result = try await (images, metadata)Structured Concurrency Gotcha Table
结构化并发问题排查表
| Gotcha | Symptom | Fix |
|---|---|---|
| async let unused | Work still executes but result is discarded silently | Assign all async let results or use withDiscardingTaskGroup |
| TaskGroup accumulating memory | Memory grows with 10K+ tasks | Process 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 failure | Some tasks succeed, some fail | Use group.next() and handle errors individually |
| async let in loop | Compiler error — async let must be in fixed positions | Use TaskGroup instead |
| Returning from group early | Remaining tasks still run | Call group.cancelAll() before returning |
| 问题 | 症状 | 修复方案 |
|---|---|---|
| async let未使用 | 工作仍会执行,但结果被静默丢弃 | 接收所有async let的结果,或使用withDiscardingTaskGroup |
| TaskGroup内存累积 | 任务数量超过10K时内存增长 | 实时处理结果,不要全部收集 |
| 在addTask中捕获可变状态 | "Mutation of captured var"错误 | 使用let绑定或Actor |
| 未处理部分失败 | 部分任务成功,部分失败 | 使用group.next()并单独处理错误 |
| 在循环中使用async let | 编译器错误——async let必须在固定位置使用 | 改用TaskGroup |
| 提前从Group返回 | 剩余任务仍在运行 | 返回前调用group.cancelAll() |
Part 5: Async Sequences
第五部分:异步序列
AsyncStream
AsyncStream
Non-throwing stream for producing values over time.
swift
let stream = AsyncStream<Int> { continuation in
for i in 0..<10 {
continuation.yield(i)
}
continuation.finish()
}
for await value in stream {
print(value)
}用于随时间生成值的非抛出型流。
swift
let stream = AsyncStream<Int> { continuation in
for i in 0..<10 {
continuation.yield(i)
}
continuation.finish()
}
for await value in stream {
print(value)
}AsyncThrowingStream
AsyncThrowingStream
Stream that can fail with an error.
swift
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)
}可能会抛出错误的流。
swift
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
Continuation API
swift
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
}swift
let stream = AsyncStream<Value> { continuation in
// 发送一个值
continuation.yield(value)
// 正常结束流
continuation.finish()
// 当消费者取消或流结束时清理资源
continuation.onTermination = { @Sendable termination in
switch termination {
case .cancelled:
cleanup()
case .finished:
finalCleanup()
@unknown default:
break
}
}
}
// 对于抛出型流
let stream = AsyncThrowingStream<Value, Error> { continuation in
continuation.yield(value)
continuation.finish() // 正常结束
continuation.finish(throwing: error) // 带错误结束
}Buffering Policies
缓冲策略
Control what happens when values are produced faster than consumed.
swift
// 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
// ...
}| Policy | Behavior | Use When |
|---|---|---|
| Keeps all values | Consumer keeps up, or bounded producer |
| Drops new values when full | Order matters, older values have priority |
| Drops old values when full | Latest state matters (UI updates, sensor data) |
控制当值生成速度快于消费速度时的行为。
swift
// 保留所有值(默认)——内存可能无限制增长
let stream = AsyncStream<Int>(bufferingPolicy: .unbounded) { continuation in
// ...
}
// 保留最早的N个值,缓冲区满时丢弃新值
let stream = AsyncStream<Int>(bufferingPolicy: .bufferingOldest(100)) { continuation in
// ...
}
// 保留最新的N个值,缓冲区满时丢弃旧值
let stream = AsyncStream<Int>(bufferingPolicy: .bufferingNewest(100)) { continuation in
// ...
}| 策略 | 行为 | 使用场景 |
|---|---|---|
| 保留所有值 | 消费者能跟上生产速度,或生产者有界 |
| 缓冲区满时丢弃新值 | 顺序重要,旧值优先级更高 |
| 缓冲区满时丢弃旧值 | 最新状态重要(UI更新、传感器数据) |
Custom AsyncSequence
自定义AsyncSequence
swift
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
}swift
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)
}
}
// 使用方式
for await number in Counter(limit: 5) {
print(number) // 0, 1, 2, 3, 4
}AsyncSequence Operators
AsyncSequence操作符
Standard operators work on any :
AsyncSequenceswift
// 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, +)标准操作符可用于任何:
AsyncSequenceswift
// 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
内置AsyncSequence
swift
// 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)
}swift
// 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
异步序列问题排查表
| Gotcha | Symptom | Fix |
|---|---|---|
| Continuation yielded after finish | Runtime warning, value lost | Track finished state, guard before yield |
| Stream never finishing | for-await loop hangs forever | Always call continuation.finish() in all code paths |
| No onTermination handler | Resource leak when consumer cancels | Set continuation.onTermination for cleanup |
| Unbounded buffer | Memory growth under load | Use .bufferingNewest(N) or .bufferingOldest(N) |
| Multiple consumers | Only first consumer gets values | AsyncStream is single-consumer; create separate streams per consumer |
| for-await on MainActor | UI freezes waiting for values | Use Task {} to consume off the main path |
| 问题 | 症状 | 修复方案 |
|---|---|---|
| finish后调用yield | 运行时警告,值丢失 | 跟踪完成状态,调用yield前检查 |
| 流从未结束 | for-await循环永远挂起 | 在所有代码路径中调用continuation.finish() |
| 无onTermination处理程序 | 消费者取消时资源泄漏 | 设置continuation.onTermination进行清理 |
| 无界缓冲区 | 负载下内存增长 | 使用.bufferingNewest(N)或.bufferingOldest(N) |
| 多个消费者 | 只有第一个消费者能获取值 | AsyncStream是单消费者的;为每个消费者创建单独的流 |
| 在MainActor上使用for-await | UI冻结等待值 | 使用Task {}在主线程之外消费 |
Part 6: Isolation Patterns
第六部分:隔离模式
@MainActor on Functions
函数上的@MainActor
swift
@MainActor
func updateUI() {
label.text = "Done"
}
// Call from async context
func doWork() async {
let result = await computeResult()
await updateUI() // Hops to MainActor
}swift
@MainActor
func updateUI() {
label.text = "Done"
}
// 从异步上下文调用
func doWork() async {
let result = await computeResult()
await updateUI() // 切换到MainActor
}MainActor.run
MainActor.run
Explicitly execute a closure on the main actor from any context.
swift
func processData() async {
let result = await heavyComputation()
await MainActor.run {
self.label.text = result
self.progressView.isHidden = true
}
}从任何上下文显式在主Actor上执行闭包。
swift
func processData() async {
let result = await heavyComputation()
await MainActor.run {
self.label.text = result
self.progressView.isHidden = true
}
}MainActor.assumeIsolated (iOS 17+)
MainActor.assumeIsolated (iOS 17+)
Assert that code is already running on the main actor. Crashes at runtime if the assertion is false.
swift
func legacyCallback() {
// We KNOW this is called on main thread (UIKit guarantee)
MainActor.assumeIsolated {
self.viewModel.update() // Access @MainActor state
}
}See for comprehensive patterns.
axiom-assume-isolated断言代码已在主Actor上运行。如果断言为假,运行时会崩溃。
swift
func legacyCallback() {
// 我们确定此方法在主线程上调用(UIKit保证)
MainActor.assumeIsolated {
self.viewModel.update() // 访问@MainActor状态
}
}有关完整模式,请参考。
axiom-assume-isolatednonisolated
nonisolated
Opt out of the enclosing actor's isolation.
swift
@MainActor
class ViewModel {
let id: UUID // Implicitly nonisolated (let)
nonisolated var analyticsID: String { // Explicitly nonisolated
id.uuidString
}
var items: [Item] = [] // Isolated to MainActor
}退出封闭Actor的隔离。
swift
@MainActor
class ViewModel {
let id: UUID // 隐式非隔离(let)
nonisolated var analyticsID: String { // 显式非隔离
id.uuidString
}
var items: [Item] = [] // 隔离到MainActor
}nonisolated(unsafe)
nonisolated(unsafe)
Compiler escape hatch. Tells the compiler to treat a property as if it's not isolated, without any safety guarantees.
swift
// 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: provides zero runtime protection. Data races will not be caught. Use only as a last resort for bridging legacy code.
nonisolated(unsafe)编译器的逃生舱口。告诉编译器将属性视为非隔离,但不提供任何安全保证。
swift
// 仅当你有外部线程安全保证时使用
nonisolated(unsafe) var legacyState: Int = 0
// 常用于编译器无法验证的全局常量
nonisolated(unsafe) let formatter: DateFormatter = {
let f = DateFormatter()
f.dateStyle = .medium
return f
}()警告:不提供任何运行时保护。数据竞争不会被捕获。仅作为桥接遗留代码的最后手段使用。
nonisolated(unsafe)@preconcurrency
@preconcurrency
Suppress concurrency warnings for pre-concurrency APIs during migration.
swift
// 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
}
}在迁移过程中,抑制针对预并发API的并发警告。
swift
// 抑制整个模块的警告
@preconcurrency import MyLegacyFramework
// 抑制特定协议一致性的警告
class MyDelegate: @preconcurrency SomeLegacyDelegate {
func delegateCallback() {
// 此一致性不会有Sendable警告
}
}#isolation (Swift 5.9+)
#isolation (Swift 5.9+)
Capture the caller's isolation context so a function runs on whatever actor the caller is on.
swift
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
}
}捕获调用者的隔离上下文,使函数在调用者所在的任何Actor上运行。
swift
func doWork(isolation: isolated (any Actor)? = #isolation) async {
// 在调用者的Actor上运行——如果调用者已隔离,则无需切换
performWork()
}
// 从@MainActor调用——在MainActor上运行
@MainActor
func setup() async {
await doWork() // doWork在MainActor上运行
}
// 从自定义Actor调用——在该Actor上运行
actor MyActor {
func run() async {
await doWork() // doWork在MyActor上运行
}
}Isolation Gotcha Table
隔离问题排查表
| Gotcha | Symptom | Fix |
|---|---|---|
| MainActor.run from MainActor | Unnecessary hop, potential deadlock risk | Check context or use assumeIsolated |
| nonisolated(unsafe) data race | Crash at runtime, corrupted state | Use proper isolation or Mutex |
| @preconcurrency hiding real issues | Runtime crashes in production | Migrate to proper concurrency before shipping |
| #isolation not available pre-5.9 | Compiler error | Use traditional @MainActor annotation |
| nonisolated on actor method | Can't access any isolated state | Only use for computed properties from non-isolated state |
| 问题 | 症状 | 修复方案 |
|---|---|---|
| 从MainActor调用MainActor.run | 不必要的上下文切换,潜在死锁风险 | 检查上下文或使用assumeIsolated |
| nonisolated(unsafe)导致数据竞争 | 运行时崩溃,状态损坏 | 使用正确的隔离或Mutex |
| @preconcurrency隐藏真实问题 | 生产环境中运行时崩溃 | 上线前迁移到正确的并发模式 |
| Swift 5.9之前不支持#isolation | 编译器错误 | 使用传统的@MainActor注解 |
| Actor方法上的nonisolated | 无法访问任何隔离状态 | 仅用于从非隔离状态计算的属性 |
Part 7: Continuations
第七部分:Continuation
Bridge callback-based APIs to async/await.
将基于回调的API桥接到async/await。
withCheckedContinuation
withCheckedContinuation
Non-throwing bridge.
swift
func currentLocation() async -> CLLocation {
await withCheckedContinuation { continuation in
locationManager.requestLocation { location in
continuation.resume(returning: location)
}
}
}非抛出型桥接。
swift
func currentLocation() async -> CLLocation {
await withCheckedContinuation { continuation in
locationManager.requestLocation { location in
continuation.resume(returning: location)
}
}
}withCheckedThrowingContinuation
withCheckedThrowingContinuation
Throwing bridge.
swift
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)
}
}
}
}抛出型桥接。
swift
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
Continuation恢复方法
swift
// 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>swift
// 返回一个值
continuation.resume(returning: value)
// 抛出一个错误
continuation.resume(throwing: error)
// 从Result类型恢复
continuation.resume(with: result) // Result<T, Error>Resume-Exactly-Once Rule
恰好恢复一次规则
A continuation MUST be resumed exactly once:
- Resuming twice crashes with (checked) or undefined behavior (unsafe)
"Continuation already resumed" - Never resuming causes the awaiting task to hang forever — a silent leak
swift
// 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)
}
}
}一个Continuation必须恰好被恢复一次:
- 恢复两次会导致崩溃(checked类型)或未定义行为(unsafe类型)
"Continuation already resumed" - 从未恢复会导致等待的任务永远挂起——静默泄漏
swift
// 危险:回调可能不会被调用
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
}
// 错误:如果两者都为nil,Continuation永远不会被恢复
// 修复:添加回退
continuation.resume(throwing: BridgeError.noResponse)
}
}
}Bridging Delegates
桥接Delegate
swift
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
}
}swift
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 // 防止重复恢复
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
continuation?.resume(throwing: error)
continuation = nil
}
}Unsafe Continuations
Unsafe Continuation
Skip runtime checks for performance. Same API as checked, but misuse causes undefined behavior instead of a diagnostic crash.
swift
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.
跳过运行时检查以提升性能。API与checked类型相同,但误用会导致未定义行为而非诊断性崩溃。
swift
func fastBridge() async -> Data {
await withUnsafeContinuation { continuation in
// 没有运行时检查来防止重复恢复或未恢复
fastCallback { data in
continuation.resume(returning: data)
}
}
}开发期间使用checked continuation,仅在经过全面测试且性能分析显示检查是瓶颈时,才切换到unsafe类型。
Continuation Gotcha Table
Continuation问题排查表
| Gotcha | Symptom | Fix |
|---|---|---|
| Resume called twice | "Continuation already resumed" crash | Set continuation to nil after resume |
| Resume never called | Task hangs indefinitely | Ensure all code paths resume — including error/nil cases |
| Capturing continuation | Continuation escapes scope | Store in property, ensure single resume |
| Unsafe continuation in debug | No diagnostics for misuse | Use withCheckedContinuation during development |
| Delegate called multiple times | Crash on second resume | Use AsyncStream instead of continuation for repeated callbacks |
| Callback on wrong thread | Doesn't matter for continuation | Continuations can be resumed from any thread |
| 问题 | 症状 | 修复方案 |
|---|---|---|
| 恢复被调用两次 | "Continuation already resumed"崩溃 | 恢复后将continuation设为nil |
| 从未恢复 | 任务无限期挂起 | 确保所有代码路径都恢复——包括错误/空值情况 |
| 捕获continuation | Continuation逃逸出作用域 | 存储在属性中,确保仅恢复一次 |
| 调试时使用Unsafe continuation | 误用无诊断信息 | 开发期间使用withCheckedContinuation |
| Delegate被多次调用 | 第二次恢复时崩溃 | 对于重复回调,改用AsyncStream而非continuation |
| 回调在错误线程上调用 | 对continuation无影响 | Continuation可从任何线程恢复 |
Part 8: Migration Patterns
第八部分:迁移模式
Common migrations from GCD and completion handlers to Swift concurrency.
从GCD和完成处理程序迁移到Swift并发的常见模式。
DispatchQueue to Actor
DispatchQueue到Actor
swift
// 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
}
}swift
// 之前:使用DispatchQueue保证线程安全
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 }
}
}
// 之后:使用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
DispatchGroup到TaskGroup
swift
// 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)swift
// 之前:使用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) }
// 之后:使用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
完成处理程序到async
swift
// 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
}swift
// 之前
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()
}
// 之后
func fetchData() async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}@objc Delegates with @MainActor
带@MainActor的@objc Delegate
swift
@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)
}
}swift
@MainActor
class ViewController: UIViewController, UITableViewDelegate {
// @objc delegate方法从类继承@MainActor隔离
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 已在MainActor上——可安全更新UI
updateSelection(indexPath)
}
}NotificationCenter to AsyncSequence
NotificationCenter到AsyncSequence
swift
// 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 neededswift
// 之前
let observer = NotificationCenter.default.addObserver(
forName: .didUpdate, object: nil, queue: .main
) { notification in
handleUpdate(notification)
}
// 必须在deinit中移除观察者
// 之后
let task = Task {
for await notification in NotificationCenter.default.notifications(named: .didUpdate) {
await handleUpdate(notification)
}
}
// 在deinit中取消任务——无需手动移除观察者Timer to AsyncSequence
Timer到AsyncSequence
swift
// 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 deinitswift
// 之前
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
updateUI()
}
// 必须在deinit中失效
// 之后
let task = Task {
while !Task.isCancelled {
await updateUI()
try? await Task.sleep(for: .seconds(1))
}
}
// 在deinit中取消任务DispatchSemaphore to Actor
DispatchSemaphore到Actor
swift
// 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()
}swift
// 之前:使用Semaphore限制并发操作数量
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
DispatchQueue.global().async {
semaphore.wait()
defer { semaphore.signal() }
download(url)
}
}
// 之后:使用带有限制并发的TaskGroup
await withTaskGroup(of: Void.self) { group in
var inFlight = 0
for url in urls {
if inFlight >= 3 {
await group.next() // 等待一个任务完成
inFlight -= 1
}
group.addTask { await download(url) }
inFlight += 1
}
await group.waitForAll()
}Migration Gotcha Table
迁移问题排查表
| Gotcha | Symptom | Fix |
|---|---|---|
| DispatchQueue.sync to actor | Deadlock potential | Remove .sync, use await |
| Global dispatch to actor contention | Slowdown from serialization | Profile with Concurrency Instruments |
| Legacy delegate + Sendable | "Cannot conform to Sendable" | Use @preconcurrency import or @MainActor isolation |
| Callback called multiple times | Continuation crash | Use AsyncStream instead of continuation |
| Semaphore.wait in async context | Thread starvation, potential deadlock | Use TaskGroup with manual concurrency limiting |
| DispatchQueue.main.async to MainActor | Subtle timing differences | MainActor.run is the equivalent — test edge cases |
| 问题 | 症状 | 修复方案 |
|---|---|---|
| DispatchQueue.sync到Actor | 潜在死锁 | 移除.sync,使用await |
| 全局调度到Actor导致竞争 | 序列化导致性能下降 | 使用并发工具进行性能分析 |
| 遗留Delegate + Sendable | "Cannot conform to Sendable"错误 | 使用@preconcurrency import或@MainActor隔离 |
| 回调被多次调用 | Continuation崩溃 | 改用AsyncStream而非continuation |
| 异步上下文中的DispatchSemaphore.wait | 线程饥饿,潜在死锁 | 使用带手动并发限制的TaskGroup |
| DispatchQueue.main.async到MainActor | 细微的时序差异 | MainActor.run是等效方案——测试边缘情况 |
API Quick Reference
API快速参考
| Task | API | Swift Version |
|---|---|---|
| Define isolated type | | 5.5+ |
| Run on main thread | | 5.5+ |
| Mark as safe to share | | 5.5+ |
| Mark closure safe to share | | 5.5+ |
| Parallel tasks (fixed) | | 5.5+ |
| Parallel tasks (dynamic) | | 5.5+ |
| Stream values | | 5.5+ |
| Bridge callback | | 5.5+ |
| Check cancellation | | 5.5+ |
| Task-scoped values | | 5.5+ |
| Assert isolation | | 5.9+ (iOS 17+) |
| Capture caller isolation | | 5.9+ |
| Lock-based sync | | 6.0+ (iOS 18+) |
| Discard results | | 5.9+ (iOS 17+) |
| Transfer ownership | | 6.0+ |
| Force background | | 6.2+ |
| Isolated conformance | | 6.2+ |
| 任务 | API | Swift版本 |
|---|---|---|
| 定义隔离类型 | | 5.5+ |
| 在主线程运行 | | 5.5+ |
| 标记为可安全共享 | | 5.5+ |
| 标记闭包可安全共享 | | 5.5+ |
| 并行任务(固定数量) | | 5.5+ |
| 并行任务(动态数量) | | 5.5+ |
| 生成值流 | | 5.5+ |
| 桥接回调 | | 5.5+ |
| 检查取消 | | 5.5+ |
| 任务作用域值 | | 5.5+ |
| 断言隔离 | | 5.9+ (iOS 17+) |
| 捕获调用者隔离 | | 5.9+ |
| 基于锁的同步 | | 6.0+ (iOS 18+) |
| 丢弃结果 | | 5.9+ (iOS 17+) |
| 转移所有权 | | 6.0+ |
| 强制后台运行 | | 6.2+ |
| 隔离式一致性 | | 6.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
WWDC:2021-10132, 2021-10134, 2022-110350, 2025-268
文档:/swift/concurrency, /swift/actor, /swift/sendable, /swift/taskgroup
技能:swift-concurrency, assume-isolated, synchronization, concurrency-profiling