`@preconcurrency` 完全导读:让旧代码平安驶上 Swift 并发快车道

一、为什么会出现 @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 编译

  1. 老库源码(无法修改)
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")
        }
    }
}
  1. Swift 6 客户端报红
swift 复制代码
import OldKit

Task {
    let fetcher = DataFetcher()
    fetcher.fetchData { print($0) }   // ❌ Type does not conform to Sendable
}
  1. 模块级通行证
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 语言模式下的真实迁移流程

  1. 开启 -swift-version 6 → 报红爆炸
  2. 先加模块级 @preconcurrency import → 编译通过
  3. 单元测试/静态分析确保无数据竞争
  4. 逐步给类型加上 Sendable / @MainActor
  5. 删除细粒度 @preconcurrency → 完成现代化

六、安全准则:通行证 ≠ 免死金牌

✅ 安全使用 checklist

  • 类型无共享可变状态(或已加锁)
  • 函数不访问全局变量/单例
  • 回调仅在内部同步执行,不逃逸

❌ 危险示例

swift 复制代码
@preconcurrency class UnsafeCache {
    var dict: [String: Any] = [:]   // 可变 + 非 Sendable
}

→ 虽然编译通过,但多个 Task 同时写 dict 仍会数据竞争!

七、与 @Sendable 的协作关系

策略 编译通过 线程安全保证 推荐场景
@preconcurrency 人工负责 迁移过渡期
真正 Sendable 编译器检查 长期目标

口诀: "先通行证,后真护照;先上车,后补票。"

八、常见编译错误对照

错误 原因 修复
@preconcurrencycannot 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 并发的快车道------既不掉链,也不甩锅。

相关推荐
大熊猫侯佩2 天前
10 个 Xcode 神技:哥谭开发者必学的 IDE 对抗术
xcode·swift·apple
HarderCoder2 天前
Swift Package Command Plugin 实战:一键生成 Package 元数据
swift
低调小一2 天前
Swift 语法学习指南 - 与 Kotlin 对比
微信·kotlin·swift
HarderCoder2 天前
Swift Package Plugin 深度实战:从原理到落地,自动生成字体枚举
swift
东坡肘子2 天前
从开放平台到受控生态:谷歌宣布 Android 开发者验证政策 | 肘子的 Swift 周报 #0101
android·swiftui·swift
HarderCoder2 天前
用 `defer` 管理异步清理:Swift 中的“保险丝”模式
swift
大熊猫侯佩3 天前
冰火岛 Tech 传:Apple Foundation Models 心法解密(上集)
llm·ai编程·swift
HarderCoder3 天前
深入理解 SwiftUI 的 Structural Identity:为什么“换个条件分支”就会丢状态?
swiftui·swift
HarderCoder3 天前
Swift Continuations 完全指南:一口气弄懂 4 种“桥梁”
swift