移动/游戏
修复 Swift 6 并发编译错误并编写数据竞争安全的异步代…
当遇到 Sendable 一致性错误、actor 隔离警告或严格并发诊断,或需要采用默认 MainActor 隔离、@concurrent、结构化并发等现代 Swift 并发模式时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
修复 Swift 6 并发编译错误并编写通用
解决思路
- 按三步分诊流程处理:第一步捕获上下文——复制确切的编译器诊断和涉事符号,确认项目的 Swift 语言版本(需 6.2+)、Xcode/工具链版本、是否启用 Approachable Concurrency、Default Actor Isolation 是否设为 MainActor、Swift 6 严格并发状态,判断代码当前的 actor 上下文(@MainActor、自定义 actor、nonisolated)以及是否有默认隔离模式,并确认代码是 UI 绑定还是应在主 actor 之外运行。
- 第二步应用最小安全修复:UI 绑定类型用 @MainActor 标注类型或相关成员;MainActor 类型上的协议一致性使用隔离一致性 `extension Foo: @MainActor Proto`;全局/静态状态用 @MainActor 保护或移入 actor;需要后台工作时在 nonisolated 类型上使用 @concurrent 异步函数;Sendable 错误优先使用不可变值类型,仅在正确时添加 Sendable;跨隔离回调使用 sending 参数(SE-0430)获得更细粒度控制。
- 第三步验证:重新构建确认诊断已解决,检查修复是否引入新警告,确保没有添加不必要的 @unchecked Sendable 或 nonisolated(unsafe);对于构建设置审查,止步于设置加最小的代码级补救,除非提示要求诊断或迁移,否则不要添加 Thread Sanitizer、宽泛的迁移顺序或架构建议。
- 采用 SE-0466 默认 MainActor 隔离:通过 `-default-isolation MainActor` 编译器标志、SwiftPM `.defaultIsolation(MainActor.self)` 或 Xcode 的 Default Actor Isolation 设为 MainActor,使模块中未标注的声明被推断为 @MainActor,除非显式选择退出;这消除了 UI 绑定代码和全局/静态状态的大部分数据竞争安全错误。
- 理解 SE-0461 nonisolated(nonsending):Swift 6.2+ 中 nonisolated 异步函数默认停留在调用者的 actor 上,而不是跳转到全局并发执行器;需要后台执行时用 @concurrent 显式请求。
- 使用 @concurrent 属性:确保函数始终在并发线程池上运行,释放调用 actor 去执行其他任务;将函数移到后台线程需同时展示两个选择退出——确保包含类型是 nonisolated 或函数可从 nonisolated 上下文调用,给卸载函数添加 @concurrent(仅 nonisolated 不会把 CPU 密集工作移出调用者 actor),如尚未异步则添加 async,在调用点添加 await。
- 使用 SE-0472 Task.immediate:在当前 actor 上同步开始执行,先于任何挂起点,而不是被入队;用于延迟敏感的工作;还有 Task.immediateDetached 结合立即开始与分离语义。
- 使用 SE-0475 事务性观察 Observations:通过 AsyncSequence 对 @Observable 类型进行异步观察,实现事务性变更跟踪。
- 使用隔离一致性:需要 MainActor 状态的一致性称为隔离一致性,编译器确保它只在匹配的隔离上下文中使用;例如 `extension StickerModel: @MainActor Exportable`,在 nonisolated 上下文中使用会报错。
- 使用 Clock Epochs(SE-0473):ContinuousClock 和 SuspendingClock 现在暴露 .epoch,支持时钟类型间的即时比较和转换。
- 遵守 Actor 隔离规则:所有可变共享状态必须由 actor 或全局 actor 保护;所有接触 UI 的代码用 @MainActor,无例外;nonisolated 仅用于访问不可变(let)属性或纯计算的方法;用 @concurrent 显式将工作移出调用者 actor;除非已证明数据竞争安全且穷尽更安全替代方案,否则绝不用 nonisolated(unsafe);绝不在 actor 内添加手动锁(NSLock、DispatchSemaphore)。
- 遵守 Sendable 规则:值类型(struct、enum)当所有存储属性都是 Sendable 时自动 Sendable;actor 隐式 Sendable;@MainActor 类隐式 Sendable,不要添加冗余的 Sendable 一致性;非 actor 类必须是 final 且所有存储属性为 let 且 Sendable;@unchecked Sendable 是最后手段,需记录编译器无法证明安全的原因;用 sending 参数(SE-0430)获得更细粒度隔离控制;@preconcurrency import 仅用于无法修改的第三方库,并计划移除。
- 使用结构化并发模式:Task 是非结构化、继承调用者上下文;Task.detached 不继承上下文,仅在明确需要打破隔离继承时使用;Task.immediate 在当前 actor 上立即开始,用于延迟敏感工作;async let 用于固定数量的并发操作;TaskGroup 用于动态数量的并发操作。
- Swift 6.4+ 的异步 defer(SE-0493):异步上下文中的 defer 块可包含 await,用于异步清理(关闭连接、刷新缓冲区、释放需要异步调用的资源);defer 体继承周围隔离并在作用域退出时隐式等待,不抑制取消。
- 处理任务取消:取消是协作式的,在循环中检查 Task.isCancelled 或调用 try Task.checkCancellation();SwiftUI 中用 .task 修饰符(视图消失时处理取消);用 withTaskCancellationHandler 做清理;Swift 6.4 / iOS 27+ beta 中仅对取消后必须完成的短清理或回滚使用 withTaskCancellationShield;在 deinit 或 onDisappear 中取消存储的任务。
- 处理 actor 重入:actor 是可重入的,状态可能在挂起点之间改变;错误做法是在 await 前后假设状态不变(如先读 count 再 await 再写回),正确做法是同步变更状态(如 `func increment() { count += 1 }`)。
- 用 AsyncStream 桥接回调/委托 API:在 AsyncStream 闭包中创建委托,通过 continuation.yield 产出值,设置 continuation.onTermination 停止委托;单值回调用 withCheckedContinuation / withCheckedThrowingContinuation,确保恰好恢复一次。
- 处理 @Observable 与并发:@Observable 类作为视图模型应为 @MainActor;用 @State 拥有 @Observable 实例(替代 @StateObject);用 Observations { }(SE-0475)将 @Observable 属性作为 AsyncSequence 异步观察。
- 在 actor 不合适时使用同步原语:需要同步访问、性能关键路径或桥接 C/ObjC 时——Mutex<Value>(iOS 18+,Synchronization 模块)是新代码首选锁,用 withLock { } 模式;OSAllocatedUnfairLock(iOS 16+,os 模块)用于旧 iOS 版本,支持所有权断言调试;Atomic<Value>(iOS 18+)用于独立计数器和标志,.relaxed 仅用于独立指标,协调其他数据时用 acquire/release 顺序或锁。关键规则:绝不在 actor 内放锁(双重同步),绝不在 await 期间持有锁。用运行时 `if #available(iOS 18, *)` 门控 Mutex 和 Atomic,绝不用 #if swift(...) 或平台编译时检查。
- 避免常见错误:不要在 @MainActor 上做重计算阻塞 UI(移到 @concurrent 函数);不要给网络层、数据处理、模型代码加不必要的 @MainActor;无状态代码不要用 actor(用普通 struct 或函数);不可变数据不要用 actor(用 Sendable struct);无充分理由不要用 Task.detached(丢失优先级、任务局部值和取消传播);不要忘记任务取消;长生命周期存储任务中捕获 self 用 [weak self] 避免循环引用;异步上下文中不要用信号量(DispatchSemaphore.wait() 可能阻塞协作执行器线程并死锁);不要在一个类型中混合 @MainActor 和 nonisolated 属性(一致地隔离整个类型);优先用 @MainActor func 而非 await MainActor.run { };新异步编排默认不要用 GCD,优先 async/await、actor 和任务组,仅在 API 需要队列、自定义执行器或有界遗留互操作时保留 dispatch 队列。
- 按审查清单逐项检查:所有可变共享状态已 actor 隔离;无数据竞争;任务不再需要时被取消;@MainActor 上无阻塞调用;actor 内无手动锁;Sendable 一致性正确(无不合理的 @unchecked);处理了 actor 重入(await 之间无状态假设);@preconcurrency 导入有文档和移除计划;重工作用 @concurrent 而非 @MainActor;SwiftUI 中用 .task 修饰符而非手动 Task 管理;同步并行循环证明了无冲突内存访问和指针生命周期不变量。
适用边界 · 注意事项
- 库目标应保持 actor 无关,不推荐启用默认 MainActor 隔离(SE-0466 推荐用于应用、脚本等大多数代码是 UI 绑定的可执行目标)。
- 不要用 nonisolated(unsafe) 作为同步替代品,除非已证明数据竞争安全且穷尽更安全替代方案;它是非安全审计边界,不是同步原语。
- 不要用 #if swift(...) 或平台编译时检查门控 Mutex 和 Atomic,应使用运行时 if #available(iOS 18, *)。