关于 axiom-swift-concurrency-ref
面向开发者的 Swift 并发 API 参考技能,提供语法查询和可复用示例,涵盖 Actor、Sendable、任务管理、取消处理及常见并发问题。
主要功能
- 01Actor 隔离与重入
- 02Sendable 遵循模式
- 03Task 创建与取消
- 04任务优先级和局部值
- 05超时与引用循环模式
- 06并发常见问题指引
适用场景
- 01查询 Actor 隔离规则
- 02解决 Sendable 编译错误
- 03管理任务取消
- 04为异步操作添加超时
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-swift-concurrency-ref安装后在 Claude Code 中输入/axiom-swift-concurrency-ref确认已加载。
详细介绍
Swift 并发 API 参考
完整的 Swift 并发 API 参考,提供可复制粘贴的模式和语法查询。
补充 axiom-swift-concurrency(介绍何时以及为何使用并发,包括渐进式学习路径、决策树、@concurrent 和隔离化遵循)。
相关技能:axiom-swift-concurrency(渐进式学习路径、决策树)、axiom-synchronization(Mutex、锁)、axiom-assume-isolated(assumeIsolated 模式)
第 1 部分:Actor 模式
Actor 定义
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)
Actor 上的所有属性和方法默认都是隔离的。Actor 隔离域之外的调用方必须使用 await 来访问它们。
Actor 隔离规则
每个 Actor 的存储属性和方法都隔离于该 Actor。跨越隔离边界进行访问需要使用 await,它会挂起调用方,直到 Actor 能够处理请求。
actor Counter {
var count = 0 // Isolated — external access requires await
let name: String // let constants are implicitly nonisolated
func increment() { // Isolated — await required from outside
count += 1
}
nonisolated func identity() -> String {
name // OK: accessing nonisolated let
}
}
let counter = Counter(name: "main")
await counter.increment() // Must await across isolation boundary
let id = counter.identity() // No await needed — nonisolated
nonisolated 关键字
退出隔离,以便同步访问不可变状态。
actor MyActor {
let id: UUID // let constants are implicitly nonisolated
nonisolated var description: String {
"Actor \(id)" // Can only access nonisolated state
}
nonisolated func hash(into hasher: inout Hasher) {
hasher.combine(id) // Only nonisolated properties
}
}
nonisolated 方法无法访问任何隔离的存储属性。可将其用于要求同步访问的协议遵循(例如 Hashable、CustomStringConvertible)。
Actor 可重入性
Actor 内的挂起点(await)允许其他调用方交错执行。任意两个 await 表达式之间的状态都可能发生变化。
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
}
}
模式:在 Actor 内的每个 await 之后重新检查状态:
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
全局 Actor 提供一个可从任何位置访问的共享隔离域。
@globalActor
actor MyGlobalActor {
static let shared = MyGlobalActor()
}
@MyGlobalActor
func doWork() { /* isolated to MyGlobalActor */ }
@MyGlobalActor
class MyService {
var state: Int = 0 // Isolated to MyGlobalActor
}
@MainActor
用于 UI 工作的内置全局 Actor。所有 UI 更新都必须在 @MainActor 上进行。
@MainActor
class ViewModel: ObservableObject {
@Published var items: [Item] = []
func loadItems() async {
let data = await fetchFromNetwork()
items = data // Safe: already on MainActor
}
}
// Annotate individual members
class MixedService {
@MainActor var uiState: String = ""
@MainActor
func updateUI() {
uiState = "Done"
}
func backgroundWork() async -> String {
await heavyComputation()
}
}
子类继承:如果一个类标记为 @MainActor,其所有子类都会继承该隔离属性。
Actor 初始化器
Actor 初始化器不具备隔离属性。你无法在 init 中调用隔离方法。
actor DataManager {
var data: [String] = []
init() {
// Cannot call isolated methods here
// self.loadDefaults() // ERROR: actor-isolated method in non-isolated init
}
// Use a factory method instead
static func create() async -> DataManager {
let manager = DataManager()
await manager.loadDefaults()
return manager
}
func loadDefaults() {
data = ["default"]
}
}
Actor 常见问题表
| 常见问题 | 症状 | 修复方法 |
|---|---|---|
| Actor 可重入性 | await 之间状态发生变化 | 每次 await 后重新检查状态 |
| nonisolated 访问隔离状态 | 编译器错误 | 移除 nonisolated,或将属性设为 nonisolated |
| 在同步上下文中调用 Actor 方法 | “Expression is 'async'” | 用 Task {} 包装,或将调用方设为异步 |
| 全局 Actor 继承 | 子类继承 @MainActor | 有意识地决定哪些方法需要隔离 |
| Actor 初始化器不具备隔离属性 | 无法在 init 中调用隔离方法 | 使用工厂方法,或在初始化后填充数据 |
| Actor 协议遵循 | “Non-isolated” 遵循错误 | 对协议方法使用 nonisolated,或使用隔离化遵循(Swift 6.2+) |
| 使用 Actor 作为 ViewModel | @Published 无法工作,UI 更新需要 await | 面向 UI 的代码使用 @MainActor 类,Actor 仅用于非 UI 共享状态 |
| 在 Actor 内使用 GCD 切换队列 | 会破坏隔离保证,并有线程爆炸风险 | 移除 GCD——Actor 隔离已能串行化访问 |
第 2 部分:Sendable 模式
自动 Sendable 遵循
当值类型的所有存储属性都满足 Sendable 时,该类型也满足 Sendable。
// Structs: Sendable when all stored properties are Sendable
struct UserProfile: Sendable {
let name: String
let age: Int
}
// Enums: Sendable when all associated values are Sendable
enum LoadState: Sendable {
case idle
case loading
case loaded(String) // String is Sendable
case failed(Error) // ERROR: Error is not Sendable
}
// Fix: use a Sendable error type
enum LoadState: Sendable {
case idle
case loading
case loaded(String)
case failed(any Error & Sendable)
}
@Sendable 闭包
跨隔离边界传递的闭包必须标记为 @Sendable。@Sendable 闭包不能捕获可变的局部状态。
func runInBackground(_ work: @Sendable () -> Void) {
Task.detached { work() }
}
// All captured values must be Sendable
var count = 0
runInBackground {
// ERROR: capture of mutable local variable
// count += 1
}
let snapshot = count
runInBackground {
print(snapshot) // OK: let binding of Sendable type
}
@unchecked Sendable
手动保证线程安全。仅在你自行提供同步机制时使用。
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)的保护
- 你需要对正确性负责——编译器不会进行检查
条件遵循
struct Box<T> {
let value: T
}
// Box is Sendable only when T is Sendable
extension Box: Sendable where T: Sendable {}
// Standard library uses this extensively:
// Array<Element>: Sendable where Element: Sendable
// Dictionary<Key, Value>: Sendable where Key: Sendable, Value: Sendable
// Optional<Wrapped>: Sendable where Wrapped: Sendable
sending 参数修饰符(SE-0430)
将值的所有权转移到另一个隔离边界。调用方会放弃访问权。
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)
}
构建设置
控制 Xcode 中 Sendable 检查的严格程度:
| 设置 | 值 | 行为 |
|---|---|---|
SWIFT_STRICT_CONCURRENCY | minimal | 仅检查显式的 Sendable 注解 |
SWIFT_STRICT_CONCURRENCY | targeted | 检查推断的 Sendable 和闭包 |
SWIFT_STRICT_CONCURRENCY | complete | 完整的严格并发检查(Swift 6 默认值) |
Sendable 常见问题表
| 常见问题 | 症状 | 修复方法 |
|---|---|---|
| 类无法成为 Sendable | “Class cannot conform to Sendable” | 将类设为 final 且不可变,或使用带锁的 @unchecked Sendable |
| 闭包捕获了非 Sendable 类型 | “Capture of non-Sendable type” | 在捕获前复制值,或让类型符合 Sendable |
| 协议无法要求 Sendable | 泛型约束复杂 | 使用 where T: Sendable |
| @unchecked Sendable 掩盖了错误 | 运行时发生数据竞争 | 仅在锁或队列能确保安全时使用 |
| Array/Dictionary 有条件地符合 Sendable | 集合仅在 Element 符合 Sendable 时才符合 | 确保元素类型符合 Sendable |
| Error 不符合 Sendable | “Type does not conform to Sendable” | 使用 any Error & Sendable 或类型化错误 |
第 3 部分:任务管理
Task { }
创建一个非结构化任务,该任务会继承当前 Actor 上下文和优先级。
// Inherits actor context — if called from @MainActor, runs on MainActor
let task = Task {
try await fetchData()
}
// Get the result
let result = try await task.value
// Get Result<Success, Failure>
let outcome = await task.result
Task.detached { }
创建一个不继承上下文的任务。不会继承 Actor 或优先级。
Task.detached(priority: .background) {
// NOT on MainActor even if created from MainActor
await processLargeFile()
}
适用场景:必须不在调用方 Actor 上运行的后台工作。多数情况下优先使用 Task {} —— 很少需要 Task.detached。
任务取消
取消采用协作式机制。设置取消标记只是发出请求;任务必须检查并作出响应。
let task = Task {
for item in largeCollection {
// Option 1: Check boolean
if Task.isCancelled { break }
// Option 2: Throw CancellationError
try Task.checkCancellation()
await process(item)
}
}
// Request cancellation
task.cancel()
Task.sleep
将当前任务挂起一段时间。支持取消——如果在睡眠期间被取消,则抛出 CancellationError。
// Duration-based (preferred)
try await Task.sleep(for: .seconds(2))
try await Task.sleep(for: .milliseconds(500))
// Nanoseconds (older API)
try await Task.sleep(nanoseconds: 2_000_000_000)
Task.yield
主动让出执行权,以便其他任务运行。用于长时间运行的同步循环。
for i in 0..<1_000_000 {
if i.isMultiple(of: 1000) {
await Task.yield()
}
process(i)
}
任务优先级
| 优先级 | 使用场景 |
|---|---|
.userInitiated | 用户直接发起的操作,结果可见 |
.high | 与 .userInitiated 相同 |
.medium | 未指定时的默认值 |
.low | 预取、非紧急工作 |
.utility | 长时间计算,显示进度 |
.background | 维护、清理等非时间敏感工作 |
Task(priority: .userInitiated) {
await loadVisibleContent()
}
Task(priority: .background) {
await cleanupTempFiles()
}
@TaskLocal
任务作用域内的值会自动传播到子任务。
enum RequestContext {
@TaskLocal static var requestID: String?
@TaskLocal static var userID: String?
}
// Set values for a scope
RequestContext.$requestID.withValue("req-123") {
RequestContext.$userID.withValue("user-456") {
// Both values available here and in child tasks
Task {
print(RequestContext.requestID) // "req-123"
print(RequestContext.userID) // "user-456"
}
}
}
// Outside scope — values are nil
print(RequestContext.requestID) // nil
传播规则:@TaskLocal 值会传播到通过 Task {} 创建的子任务,但不会传播到 Task.detached {}。
任务超时模式
使用任务组竞速,为任意异步操作设置截止时间:
func withTimeout<T: Sendable>(
_ duration: Duration,
operation: @Sendable @escaping () async throws -> T
) async throws -> T {
try await withThrowingTaskGroup(of: T.self) { group in
group.addTask { try await operation() }
group.addTask {
try await Task.sleep(for: duration)
throw TimeoutError()
}
guard let result = try await group.next() else {
throw TimeoutError()
}
group.cancelAll() // Cancel the loser — without this it keeps running
return result
}
}
group.cancelAll() 至关重要。没有它,落败的任务(超时任务或操作任务)会继续运行,直到任务组作用域退出。
任务保留环
任务会像闭包一样捕获变量。引用 self 的已存储任务会形成保留环。
// ❌ Retain cycle: self → task → self
task = Task {
while true { await self.poll() }
}
// ✅ Weak capture breaks the cycle
task = Task { [weak self] in
while let self, !Task.isCancelled {
await self.poll()
}
}
规则:当 Task 作为属性存储,或遍历无限异步序列时,使用 [weak self]。能快速完成的短生命周期 Task 可以使用强引用捕获。
Swift 6 中的 Thread.current
在 Swift 6 语言模式下,异步上下文中无法使用 Thread.current:
// ❌ Compiler error in Swift 6 mode
func check() async { print(Thread.current) }
// ✅ Workaround for debugging only
extension Thread {
static var currentThread: Thread { Thread.current }
}
不要依赖线程标识来确保正确性——任务会在挂起点之间切换线程。应考虑隔离域。
任务常见问题表
| 常见问题 | 现象 | 修复方法 |
|---|---|---|
| 任务从未取消 | 资源泄漏,视图消失后工作仍在继续 | 存储任务,并在 deinit/onDisappear 中取消 |
| 忽略取消 | 即使已取消,任务仍会运行至完成 | 在循环中检查 Task.isCancelled,并使用 checkCancellation() |
| Task.detached 丢失 Actor 上下文 | “未隔离到 MainActor” | 需要 Actor 隔离时使用 Task {} |
| 在存储的 Task 中捕获 self | 循环引用,永远不会调用 deinit | 对长期运行或存储的任务使用 [weak self] |
| 认为 async = 后台运行 | 代码仍在调用方 Actor 上运行 | 使用 @concurrent 强制在后台执行 |
| TaskLocal 未传播 | detached 任务中的值为 nil | TaskLocal 只会传播到子任务,不会传播到 detached 任务 |
| 任务优先级反转 | 低优先级任务阻塞高优先级任务 | 系统会处理大多数情况;避免让高优先级任务等待低优先级任务 |
| 在 async 上下文中使用 Thread.current | Swift 6 模式下编译器报错 | 不要依赖线程身份,改用隔离域 |
第 4 部分:结构化并发
async let
并行运行固定数量的操作。离开作用域时,所有 async let 绑定都会被隐式等待。
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)
语义:如果某个 async let 抛出错误,其他任务会被取消。退出外围作用域前,所有任务都必须完成(或被取消)。
TaskGroup — 不抛出错误
动态运行数量不定的并行任务,且这些任务都不会抛出错误。
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 — 抛出错误
动态运行数量不定的并行任务,这些任务可能会抛出错误。
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+)
适用于需要并发,但不需要收集结果的情况。相比常规 TaskGroup 更节省内存,因为不会存储结果。
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
}
实际应用模式 — 合并多个通知流
extension NotificationCenter {
func notifications(named names: [Notification.Name]) -> AsyncStream<Void> {
AsyncStream { continuation in
let task = Task {
await withDiscardingTaskGroup { group in
for name in names {
group.addTask {
for await _ in self.notifications(named: name) {
continuation.yield()
}
}
}
}
continuation.finish()
}
continuation.onTermination = { _ in task.cancel() }
}
}
}
TaskGroup 控制
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)
}
}
任务树语义
结构化并发会形成一棵树:
- 父任务取消会取消所有子任务 — 取消一个任务会取消所有
async let和 TaskGroup 子任务 - 子任务错误会传播到父任务 — 在可抛出错误的组中,子任务错误会取消同级任务并向上传播
- 父任务返回前,所有子任务都必须完成 — 作用域会等待所有子任务,即使它们已被取消
// If fetchImages() throws, fetchMetadata() is automatically cancelled
async let images = fetchImages()
async let metadata = fetchMetadata()
let result = try await (images, metadata)
结构化并发常见问题表
| 常见问题 | 现象 | 修复方法 |
|---|---|---|
| 未使用 async let | 工作仍会执行,但结果会被静默丢弃 | 为所有 async let 结果赋值,或使用 withDiscardingTaskGroup |
| TaskGroup 累积内存 | 任务达到 1 万个以上时内存持续增长 | 结果到达时立即处理,不要全部收集 |
| 在 addTask 中捕获可变状态 | “修改捕获的 var” | 使用 let 绑定或 Actor |
| 未处理部分失败 | 部分任务成功,部分任务失败 | 使用 group.next() 并单独处理错误 |
| 在循环中使用 async let | 编译器报错 — async let 必须位于固定位置 | 改用 TaskGroup |
| 提前从组中返回 | 剩余任务仍会运行 | 返回前调用 group.cancelAll() |
第 5 部分:异步序列
AsyncStream
用于随时间生成值的非抛出错误流。
let stream = AsyncStream<Int> { continuation in
for i in 0..<10 {
continuation.yield(i)
}
continuation.finish()
}
for await value in stream {
print(value)
}
AsyncThrowingStream
可能因错误而失败的流。
let stream = AsyncThrowingStream<Data, Error> { continuation in
let monitor = NetworkMonitor()
monitor.onData = { data in
continuation.yield(data)
}
monitor.onError = { error in
continuation.finish(throwing: error)
}
monitor.onComplete = {
continuation.finish()
}
continuation.onTermination = { @Sendable _ in
monitor.stop()
}
monitor.start()
}
do {
for try await data in stream {
process(data)
}
} catch {
handleStreamError(error)
}
Continuation API
let stream = AsyncStream<Value> { continuation in
// Emit a value
continuation.yield(value)
// End the stream normally
continuation.finish()
// Cleanup when consumer cancels or stream ends
continuation.onTermination = { @Sendable termination in
switch termination {
case .cancelled:
cleanup()
case .finished:
finalCleanup()
@unknown default:
break
}
}
}
// For throwing streams
let stream = AsyncThrowingStream<Value, Error> { continuation in
continuation.yield(value)
continuation.finish() // Normal end
continuation.finish(throwing: error) // End with error
}
缓冲策略
控制值的生成速度快于消费速度时会发生什么。
// 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
// ...
}
| 策略 | 行为 | 适用场景 |
|---|---|---|
.unbounded | 保留所有值 | 消费者能跟上,或生产者有界 |
.bufferingOldest(N) | 缓冲区满时丢弃新值 | 顺序很重要,较旧的值优先 |
.bufferingNewest(N) | 缓冲区满时丢弃旧值 | 最新状态很重要(UI 更新、传感器数据) |
自定义 AsyncSequence
struct Counter: AsyncSequence {
typealias Element = Int
let limit: Int
struct AsyncIterator: AsyncIteratorProtocol {
var current = 0
let limit: Int
mutating func next() async -> Int? {
guard current < limit else { return nil }
defer { current += 1 }
return current
}
}
func makeAsyncIterator() -> AsyncIterator {
AsyncIterator(limit: limit)
}
}
// Usage
for await number in Counter(limit: 5) {
print(number) // 0, 1, 2, 3, 4
}
AsyncSequence 运算符
标准运算符适用于任何 AsyncSequence:
// Map
for await name in users.map(\.name) { }
// Filter
for await adult in users.filter({ $0.age >= 18 }) { }
// CompactMap
for await image in urls.compactMap({ await tryLoadImage($0) }) { }
// Prefix
for await first5 in stream.prefix(5) { }
// first(where:)
let match = await stream.first(where: { $0 > threshold })
// Contains
let hasMatch = await stream.contains(where: { $0 > threshold })
// Reduce
let sum = await numbers.reduce(0, +)
内置异步序列
// 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)
}
异步序列注意事项表
| 注意事项 | 表现 | 修复方法 |
|---|---|---|
| 完成后仍生成后续值 | 运行时警告,值丢失 | 跟踪完成状态,在生成值前进行检查 |
| 流始终未完成 | for-await 循环永久挂起 | 在所有代码路径中都调用 continuation.finish() |
| 没有 onTermination 处理程序 | 消费者取消时发生资源泄漏 | 设置 continuation.onTermination 以进行清理 |
| 缓冲区无界 | 高负载下内存不断增长 | 使用 .bufferingNewest(N) 或 .bufferingOldest(N) |
| 多个消费者 | 只有第一个消费者能收到值 | AsyncStream 是单消费者的;为每个消费者创建单独的流 |
| 在 MainActor 上使用 for-await | 等待值时 UI 冻结 | 使用 Task {} 在主路径之外进行消费 |
第 6 部分:隔离模式
函数上的 @MainActor
@MainActor
func updateUI() {
label.text = "Done"
}
// Call from async context
func doWork() async {
let result = await computeResult()
await updateUI() // Hops to MainActor
}
MainActor.run
从任何上下文中显式在主 Actor 上执行闭包。
func processData() async {
let result = await heavyComputation()
await MainActor.run {
self.label.text = result
self.progressView.isHidden = true
}
}
MainActor.assumeIsolated(iOS 17+)
断言代码当前已在主 Actor 上运行。如果断言为假,则在运行时崩溃。
func legacyCallback() {
// We KNOW this is called on main thread (UIKit guarantee)
MainActor.assumeIsolated {
self.viewModel.update() // Access @MainActor state
}
}
如需了解全面的模式,请参阅 axiom-assume-isolated。
nonisolated
退出外层 Actor 的隔离。
@MainActor
class ViewModel {
let id: UUID // Implicitly nonisolated (let)
nonisolated var analyticsID: String { // Explicitly nonisolated
id.uuidString
}
var items: [Item] = [] // Isolated to MainActor
}
nonisolated(unsafe)
编译器的逃生口。告知编译器将属性视为未隔离,但不提供任何安全保证。
// 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
}()
警告:nonisolated(unsafe) 不提供任何运行时保护。数据竞争不会被捕获。仅在桥接旧代码时将其作为最后手段使用。
@preconcurrency
迁移期间,抑制针对并发前 API 的并发警告。
// Suppress warnings for entire module
@preconcurrency import MyLegacyFramework
// Suppress for specific protocol conformance
class MyDelegate: @preconcurrency SomeLegacyDelegate {
func delegateCallback() {
// No Sendable warnings for this conformance
}
}
#isolation(Swift 6.0+)
捕获调用方的隔离上下文,使函数在调用方所在的任意 Actor 上运行。
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
}
}
Task 闭包中的 #isolation 捕获(SE-0420)
创建需要处理非 Sendable 类型的 Task 闭包时,捕获隔离参数以继承调用方的上下文。
func process(
delegate: NonSendableDelegate,
isolation: isolated (any Actor)? = #isolation
) {
Task {
_ = isolation // Forces capture — Task inherits caller's isolation
delegate.doWork() // ✅ Safe: running on caller's actor
}
}
为什么需要 _ = isolation:根据 SE-0420,只有当闭包捕获了隔离参数的非可选绑定时,Task 闭包才会继承隔离上下文。_ = isolation 语句会强制进行此捕获。没有它,Task 会在默认执行器上运行,捕获非 Sendable 值会导致编译器错误。
适用场景:创建需要处理非 Sendable 委托对象的 Task、需要访问调用方状态的即发即忘异步工作,或在保持委托对象存活的同时桥接基于回调的 API。
隔离注意事项表
| 注意事项 | 表现 | 修复方法 |
|---|---|---|
| 从 MainActor 调用 MainActor.run | 不必要的切换,可能存在死锁风险 | 检查上下文或使用 assumeIsolated |
| nonisolated(unsafe) 数据竞争 | 运行时崩溃,状态损坏 | 使用适当的隔离或 Mutex |
| @preconcurrency 隐藏真实问题 | 生产环境中发生运行时崩溃 | 发布前迁移到适当的并发方案 |
| 5.9 之前不可用 #isolation | 编译器错误 | 使用传统的 @MainActor 注解 |
| 在 Task 中未捕获 #isolation | 捕获非 Sendable 值时出错 | 在 Task 闭包中添加 _ = isolation(SE-0420) |
| 在 Actor 方法上使用 nonisolated | 无法访问任何隔离状态 | 仅用于基于非隔离状态的计算属性 |
| 在异步上下文中使用 Thread.current | Swift 6 模式下出现编译器错误 | 不要依赖线程标识;应基于隔离域进行推理 |
第 7 部分:续体
将基于回调的 API 桥接到 async/await。
withCheckedContinuation
非抛出桥接。
func currentLocation() async -> CLLocation {
await withCheckedContinuation { continuation in
locationManager.requestLocation { location in
continuation.resume(returning: location)
}
}
}
withCheckedThrowingContinuation
可抛出桥接。
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)
}
}
}
}
续体恢复方法
// 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>
恰好恢复一次规则
续体必须恰好恢复一次:
- 恢复两次会导致崩溃,错误为
"Continuation already resumed"(checked),或产生未定义行为(unsafe) - 从不恢复会导致等待中的任务永久挂起,造成无声泄漏
// 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)
}
}
}
桥接委托
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
}
}
不安全续体
跳过运行时检查以提升性能。API 与经过检查的版本相同,但误用会导致未定义行为,而不是诊断性崩溃。
func fastBridge() async -> Data {
await withUnsafeContinuation { continuation in
// No runtime check for double-resume or missing resume
fastCallback { data in
continuation.resume(returning: data)
}
}
}
开发期间请使用经过检查的续体,只有在彻底测试后,且性能分析显示检查已成为瓶颈时,才切换到不安全版本。
续体常见问题表
| 常见问题 | 症状 | 修复方法 |
|---|---|---|
| Resume 调用两次 | 发生“Continuation already resumed”崩溃 | 调用 resume 后将 continuation 设为 nil |
| 从未调用 Resume | 任务无限期挂起 | 确保所有代码路径都会调用 resume,包括错误和 nil 情况 |
| 捕获 continuation | continuation 超出作用域 | 将其存储在属性中,并确保只调用一次 resume |
| 在调试模式下使用不安全续体 | 误用时不会提供诊断信息 | 开发期间使用 withCheckedContinuation |
| 委托被多次调用 | 第二次调用 resume 时崩溃 | 对于重复回调,请使用 AsyncStream,而不是续体 |
| 回调在错误线程上执行 | 对续体没有影响 | 可以从任何线程调用续体的 resume |
第 8 部分:迁移模式
从 GCD 和完成处理程序迁移到 Swift 并发的常见方式。
从 DispatchQueue 迁移到 Actor
// BEFORE: DispatchQueue for thread safety
class ImageCache {
private let queue = DispatchQueue(label: "cache", attributes: .concurrent)
private var cache: [URL: UIImage] = [:]
func get(_ url: URL, completion: @escaping (UIImage?) -> Void) {
queue.async { completion(self.cache[url]) }
}
func set(_ url: URL, image: UIImage) {
queue.async(flags: .barrier) { self.cache[url] = image }
}
}
// AFTER: Actor
actor ImageCache {
private var cache: [URL: UIImage] = [:]
func get(_ url: URL) -> UIImage? {
cache[url]
}
func set(_ url: URL, image: UIImage) {
cache[url] = image
}
}
从 DispatchGroup 迁移到 TaskGroup
// BEFORE: DispatchGroup
let group = DispatchGroup()
var results: [Data] = []
for url in urls {
group.enter()
fetch(url) { data in
results.append(data)
group.leave()
}
}
group.notify(queue: .main) { use(results) }
// AFTER: TaskGroup
let results = await withTaskGroup(of: Data.self) { group in
for url in urls {
group.addTask { await fetch(url) }
}
var collected: [Data] = []
for await data in group {
collected.append(data)
}
return collected
}
use(results)
从完成处理程序迁移到 async
// BEFORE
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, error in
if let error { completion(.failure(error)); return }
guard let data else { completion(.failure(FetchError.noData)); return }
completion(.success(data))
}.resume()
}
// AFTER
func fetchData() async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
使用 @MainActor 的 @objc 委托
@MainActor
class ViewController: UIViewController, UITableViewDelegate {
// @objc delegate methods inherit @MainActor isolation from the class
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Already on MainActor — safe to update UI
updateSelection(indexPath)
}
}
从 NotificationCenter 迁移到 AsyncSequence
// BEFORE
let observer = NotificationCenter.default.addObserver(
forName: .didUpdate, object: nil, queue: .main
) { notification in
handleUpdate(notification)
}
// Must remove observer in deinit
// AFTER
let task = Task {
for await notification in NotificationCenter.default.notifications(named: .didUpdate) {
await handleUpdate(notification)
}
}
// Cancel task in deinit — no manual observer removal needed
从 Timer 迁移到 AsyncSequence
// BEFORE
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
updateUI()
}
// Must invalidate in deinit
// AFTER
let task = Task {
while !Task.isCancelled {
await updateUI()
try? await Task.sleep(for: .seconds(1))
}
}
// Cancel task in deinit
从 DispatchSemaphore 迁移到 Actor
// BEFORE: Semaphore to limit concurrent operations
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
DispatchQueue.global().async {
semaphore.wait()
defer { semaphore.signal() }
download(url)
}
}
// AFTER: TaskGroup with limited concurrency
await withTaskGroup(of: Void.self) { group in
var inFlight = 0
for url in urls {
if inFlight >= 3 {
await group.next() // Wait for one to finish
inFlight -= 1
}
group.addTask { await download(url) }
inFlight += 1
}
await group.waitForAll()
}
迁移常见问题表
| 常见问题 | 症状 | 修复方法 |
|---|---|---|
| 从 DispatchQueue.sync 迁移到 actor | 可能导致死锁 | 移除 .sync,改用 await |
| 全局派发导致 actor 竞争 | 串行化导致性能下降 | 使用 Concurrency Instruments 进行性能分析 |
| 遗留委托 + Sendable | “Cannot conform to Sendable” | 使用 @preconcurrency import 或 @MainActor 隔离 |
| 回调被多次调用 | 续体崩溃 | 使用 AsyncStream,而不是续体 |
| 在异步上下文中调用 Semaphore.wait | 线程饥饿,可能导致死锁 | 使用 TaskGroup 并手动限制并发 |
| 从 DispatchQueue.main.async 迁移到 MainActor | 时序上存在细微差异 | MainActor.run 是等效方式——请测试边缘情况 |
| 用顶层 Tasks 替换结构化任务 | 丢失取消传播和错误处理 | 对相关的并行工作使用 async let 或 TaskGroup |
| 批量添加 @unchecked Sendable 以修复警告 | 掩盖整个代码库中的真实数据竞争 | 一次修复一个类型,正确使用 Sendable、actor 或 sending |
API 速查
| 任务 | API | Swift 版本 |
|---|---|---|
| 定义隔离类型 | actor MyActor { } | 5.5+ |
| 在主线程上运行 | @MainActor | 5.5+ |
| 标记为可安全共享 | : Sendable | 5.5+ |
| 标记闭包为可安全共享 | @Sendable | 5.5+ |
| 并行任务(固定) | async let | 5.5+ |
| 并行任务(动态) | withTaskGroup | 5.5+ |
| 流式传递值 | AsyncStream | 5.5+ |
| 桥接回调 | withCheckedContinuation | 5.5+ |
| 检查取消状态 | Task.checkCancellation() | 5.5+ |
| 任务作用域值 | @TaskLocal | 5.5+ |
| 断言隔离 | MainActor.assumeIsolated | 5.9+ (iOS 17+) |
| 捕获调用方隔离 | #isolation | 6.0+ |
| 基于锁的同步 | Mutex | 6.0+ (iOS 18+) |
| 丢弃结果 | withDiscardingTaskGroup | 5.9+ (iOS 17+) |
| 转移所有权 | sending 参数 | 6.0+ |
| 强制在后台运行 | @concurrent | 6.2+ |
| 隔离的一致性 | extension: @MainActor Proto | 6.2+ |
资源
WWDC:2021-10132、2021-10134、2022-110350、2025-268
文档:/swift/concurrency、/swift/actor、/swift/sendable、/swift/taskgroup
技能:swift-concurrency、assume-isolated、synchronization、concurrency-profiling