一、为什么会出现 @preconcurrency
?
Swift 5.5+ 的并发模型要求:
- 跨任务传递的类型必须
Sendable
- 访问共享状态需隔离(
@MainActor
/actor
) - 编译器静态检查上述规则
但现实是:
- 公司祖传框架写于 Swift 5.0
- 第三方库没加
@Sendable
/@MainActor
- 系统 Objective-C 头文件更老
于是 Xcode 开始疯狂报红:
css
Type 'DataFetcher' does not conform to 'Sendable'
Call to main actor-isolated instance method in a synchronous context
@preconcurrency
就是"临时通行证":
"老代码我保证安全使用,请先让我编译通过。"
二、能贴在哪儿?一张表看全
目标 | 示例 | 效果 |
---|---|---|
class/struct/enum/actor | @preconcurrency class Foo |
整个类型视为 Sendable |
protocol | @preconcurrency protocol P |
遵守者暂获豁免 |
extension | @preconcurrency extension Foo |
扩展内成员豁免 |
函数 | @preconcurrency func f() |
单个方法豁免 |
typealias | @preconcurrency typealias T = Foo |
别名豁免 |
import | @preconcurrency import OldKit |
整个模块一次性豁免 |
最常见:模块级 import 和 单个类型 声明。
三、实战:让 Swift 5.4 的老库通过 Swift 6 编译
- 老库源码(无法修改)
swift
// OldKit.swift (Swift 5.4)
public class DataFetcher {
public func fetchData(completion: @escaping (String) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
completion("Legacy Data")
}
}
}
- Swift 6 客户端报红
swift
import OldKit
Task {
let fetcher = DataFetcher()
fetcher.fetchData { print($0) } // ❌ Type does not conform to Sendable
}
- 模块级通行证
swift
@preconcurrency import OldKit // ✅ 一次性静默警告
Task {
let fetcher = DataFetcher()
fetcher.fetchData { print($0) } // 通过,但你负责线程安全
}
→ 整个模块所有符号暂时视为 Sendable
,警告消失。
四、粒度更细:只给单类型/单函数开绿灯
若能改动源码,最小化豁免:
swift
// 仅对单个类型
@preconcurrency
class LegacyCache {
func save(_ data: Data) { /* 非线程安全 */ }
}
// 仅对单个函数
extension LegacyCache {
@preconcurrency func clear() { /* 非线程安全 */ }
}
→ 调用侧同样静默,但影响面更小,便于后续逐步加固。
五、Swift 6 语言模式下的真实迁移流程
- 开启
-swift-version 6
→ 报红爆炸 - 先加模块级
@preconcurrency import
→ 编译通过 - 单元测试/静态分析确保无数据竞争
- 逐步给类型加上
Sendable
/@MainActor
- 删除细粒度
@preconcurrency
→ 完成现代化
六、安全准则:通行证 ≠ 免死金牌
✅ 安全使用 checklist
- 类型无共享可变状态(或已加锁)
- 函数不访问全局变量/单例
- 回调仅在内部同步执行,不逃逸
❌ 危险示例
swift
@preconcurrency class UnsafeCache {
var dict: [String: Any] = [:] // 可变 + 非 Sendable
}
→ 虽然编译通过,但多个 Task 同时写 dict
仍会数据竞争!
七、与 @Sendable
的协作关系
策略 | 编译通过 | 线程安全保证 | 推荐场景 |
---|---|---|---|
@preconcurrency |
✅ | 人工负责 | 迁移过渡期 |
真正 Sendable |
✅ | 编译器检查 | 长期目标 |
口诀: "先通行证,后真护照;先上车,后补票。"
八、常见编译错误对照
错误 | 原因 | 修复 |
---|---|---|
@preconcurrency cannot be applied to this declaration |
贴在不被支持的声明上 | 改用模块级 import 或换声明类型 |
Type 'Foo' does not conform to 'Sendable' | 未加通行证 | 在 import 或类型前加 @preconcurrency |
Call to main actor-isolated instance method in a synchronous context | 老代码在主线程外调 UI | 把调用包进 MainActor.run 或给方法加 @MainActor |
九、一句话总结
@preconcurrency
= "老代码的临时通行证",
它让 Swift 6 的严格检查暂时闭嘴, 但线程安全责任从此落到你的肩上!
记住口诀:
"模块 import 加头顶,单类型声明也 OK;先让项目跑起来,再逐步 Sendable。"
用好这张"临时驾照",让祖传代码平稳驶上 Swift 并发的快车道------既不掉链,也不甩锅。