Ott Maidre 拍摄的照片:https://www.pexels.com/photo/medieval-armor-2046779/
Swift 6 中有哪些新的并发编程功能?Swift 6 更加注重严格的并发检查以消除数据竞争和未定义行为。关键更新包括:
- 更严格的
**@MainActor**
强制执行:编译器现在更积极地标记未显式标记为@MainActor
的 UI 更新。 - 完全的任务隔离:函数和属性必须显式声明 actor 隔离。
- 全局 actor 的自定义:更好地控制全局 actor,如
@MainActor
或自定义的全局 actor。 - 改进的诊断:线程不安全代码的警告更明确。
当你升级到 Xcode 16 时,你可能会遇到一些新的警告和错误。下面是一些处理常见情况的方法。
示例 1:@MainActor
提示
情景:一个视图模型更新UI元素但没有使用@MainActor
,因此导致出现如下警告:
‘“Main actor-isolated property 'title' cannot be mutated from a non-isolated context”’。
之前:
class ProfileViewModel {
var title: String = "" // 警告: 这里没有使用actor隔离
func loadData() async {
let data = await fetchData()
title = data.title // 错误: 在非隔离环境下尝试修改数据
}
}
修好后:
@MainActor // 明确将整个类标记为主actor隔离的
class ProfileViewModel: ObservableObject {
@Published var title: String = ""
func loadData() async {
let data = await fetchData()
title = data.title // ✅ 在主线程上执行。
}
}
示例 2:在线程间切换使用 DispatchQueue
场景:老代码里用 DispatchQueue.main.async
来更新 UI 元素。Swift 6 认为这种做法不安全,除非将其包裹在 MainActor
里。
未修复:
func updateUI() {
DispatchQueue.main.async { // ❌ 提示:推荐使用 MainActor.run 而不是这里的代码
self.label.text = "Hello"
}
}
搞定后:
func 更新用户界面() async {
await MainActor.run { // ✅ 线程安全注释
self.label.text = "你好"
}
}
示例 3:未隔离的全局状态
场景:多个线程访问同一个共享的 UserDefaults
管理器,共享的可变状态在没有并发保护的情况下被访问,引发了如下警告:
"Shared mutable state accessed without concurrency protection". (共享的可变状态在没有并发保护的情况下被访问)
。
未修复前:
class SettingsManager {
static let shared = SettingsManager()
var theme: String = "light" // ❌ 注意:此设置可能不是线程安全的
}
修好后:
@globalActor
struct SettingsActor {
actor ActorType { }
static let shared = ActorType()
}
@SettingsActor // 自定义全局Actor用于保证线程安全
class SettingsManager {
static let shared = SettingsManager()
var theme: String = "light" // 受SettingsActor保护
}
3. 常见警告及解决方法
警告:在 @MainActor
中使用了非发送类型
"不可发送的类型 'ViewController' 传递给隔离在主演员中的方法的隐式的异步调用"
解决一下问题:
class ViewController: UIViewController {
// 当按钮被点击时触发的函数
func onButtonTap() {
// 使用 Task { @MainActor in } 将任务隔离在主线程中,以确保 UI 更新的线程安全
Task { @MainActor in // ✅ Isolate the Task
self.updateUI()
}
}
// 使用 @MainActor 关键字标记的函数会在主线程中执行,确保 UI 更新不会导致线程冲突
@MainActor
func updateUI() { // 更新 UI 的具体实现代码
// 更新 UI 的具体实现代码
}
}
警告 2: 在 @Sendable
闭包中使用 self
在 @Sendable
闭包中,使用非可发送类型 'MyClass' 来引用 'self'。
请解决:
Task { [weak self] in //✅ 使用弱引用来避免 retain 循环
guard let self else { return }
await self 的 fetchData()
}
警告 3: 在并发上下文中使用不符合 Sendable
要求的类型
"该函数不能标记为'@Sendable',因为其包含的' DataModel '类型参数不是可发送的。"
修复一下:
struct DataModel: Sendable { // ✅ 数据模型是线程安全的
let id: UUID
let value: String
}
- 如何在 Xcode 16 中调试并发代码
Swift 6 带来了一些强大的工具来检测并发错误。
1. 线程检查器 (TSan) 改进TSan 现在可以检测 Swift 并发程序中的数据争用。可以通过以下方式启用它:
产品 > 方案 > 编辑方案 > 诊断 > 线程检测器。
可视化任务全过程,可以使用以下方法:
// 使用带有任务跟踪的await获取用户数据
await withTaskTracing {
await fetchUserData()
}
3. -strict-concurrency
编译器选项
在构建设置中开启严格的并发控制:
SWIFT_STRICT_CONCURRENCY = 启用
5. 迁移旧代码的最佳实践
- 逐步采用(@preconcurrency 导入):对于尚未更新到 Swift 6 的第三方库,使用
@preconcurrency import
。 - 使用编译器警告进行审计:将警告视为错误 (
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
)。 - 用 Actors 替换
**DispatchQueue**
**:将基于 GCD 的代码替换为 Swift 原生的actor
或@MainActor
。 - 逐步使用
**Sendable**
**:将线程安全的类型标记为Sendable
以启用编译器优化。
Swift 6的并发模型是一个游戏规则的改变者,对于编写安全且高性能的应用程序来说是一个变革,但将现有代码库迁移到新的并发模型需要仔细处理@MainActor
、线程隔离性以及Sendable
类型。通过逐步解决常见的警告,并利用Xcode 16的调试工具,您可以使代码保持前瞻性,同时保持其稳定性。
要点:
- 尽量激进地使用
@MainActor
来更新 UI。 - 用 Swift 的结构化并发特性替换掉
DispatchQueue
。 - 尽早启用严格的并发检查功能。
- 审核第三方依赖项以确保它们符合
Sendable
要求。