Swift 6 开启完整并发检查后,为何没有报告 Sendable 错误?
结论
按你给出的配置,无需再添加并发检查参数。Swift 6 语言模式会启用完整并发检查。SWIFT_STRICT_CONCURRENCY = complete 主要用于在较早的语言模式下逐步检查;在 Swift 6 模式下,它不会让检查更严格。Swift 官方迁移指南和 Apple 的构建设置说明也提到了这一点:Swift 6 并发安全检查指南、Xcode 构建设置参考。
因此,如果这段代码确实以 Swift 6 模式编译,PersonViewModel 的 Sendable 声明应当触发诊断:这个类既不是 final,也包含可变的存储属性。符合 Sendable 的普通类需要满足相应约束,也可以通过 actor 隔离可变状态。Swift Sendable 文档
先核对实际参与构建的目标
git grep 只能确认这些设置写在项目文件里,无法证明当前 scheme、configuration 和 target 最终采用了它们。可以用实际构建的 scheme 检查有效设置:
xcodebuild -scheme <scheme> -configuration Debug -showBuildSettings \
| grep -E 'SWIFT_VERSION|SWIFT_STRICT_CONCURRENCY'
然后查看 Xcode 构建日志,确认包含 PersonViewModel 的 Swift 文件确实参与了该 target 的 SwiftCompile。同时检查文件是否受条件编译控制,以及相关诊断是否被编译参数抑制。必要时,可以从命令行构建同一个 scheme:
xcodebuild -scheme <scheme> -configuration Debug build
接下来要核实的是实际编译的 target 和配置,不必继续清理 DerivedData。如果有效设置确实是 Swift 6,但这段类声明仍未触发诊断,就从构建日志中的实际编译命令和源码所属 target 查起。
运行时崩溃和并发诊断是两件事
String! 是隐式解包的可选值。将 nil 赋给它是允许的;之后将它作为非可选 String 使用时,程序会尝试解包,并可能在运行时崩溃。这个崩溃本身不能证明发生了数据竞争。SwiftUI 的视图和任务闭包也可能受 actor 隔离约束,因此应以实际编译诊断为准,不能只凭两个 Task 就断定它们会并发访问该属性。
安全地表达这类状态
如果 personName 需要在并发任务之间改变,可以用 actor 管理它,并通过 actor 方法访问属性:
actor PersonViewModel {
private var personName: String? = "Default"
func clearName() {
personName = nil
}
func name() -> String? {
personName
}
}
调用方通过 await 访问状态,并显式处理可选值:
let viewModel = PersonViewModel()
Task {
await viewModel.clearName()
}
Task {
if let name = await viewModel.name() {
message = name
}
}
如果状态不需要改变,也可以将类声明为 final,并把属性设为不可变,使用可发送的类型,例如 let personName: String?。不要只为消除诊断而使用 @unchecked Sendable,因为这会把线程安全责任交给代码作者。
备注:内容仅供参考。
