Swift 6.2 新特性 `@concurrent` 完全导读

背景:为什么突然冒出 @concurrent

Swift 6.2 引入了两项默认行为变化:

  1. Main Actor 默认隔离(UI相关的Target或package)

    未显式标注隔离的代码自动视为 @MainActor

  1. nonisolated 函数行为分裂

    • async → 跑在全局执行器(后台线程)
    • async → 跑在调用者执行器(可能主线程)

这让"同一段 nonisolated 代码"在不同签名下表现不一致,容易踩坑。

于是 Swift 团队先推出 nonisolated(nonsending) 统一行为(永远留在调用者线程 ),再用 @concurrent 显式声明"我要 offload 到全局执行器"。

三条隔离路线一览

写法 跑在哪 引入新隔离域 需 Sendable
nonisolated+ async(老模式) 全局执行器
nonisolated(nonsending) 调用者执行器
@concurrent 全局执行器

口诀:"nonsending 留守,@concurrent 外派。"

语法与位置

  1. 自动隐式 nonisolated

    @concurrent 即可,不必再加 nonisolated

swift 复制代码
@concurrent
func decode<T: Decodable>(_ data: Data) async throws -> T { ... }
  1. 可放在类型内

    即使类型本身是 @MainActoractor,也能外派单个方法:

swift 复制代码
@MainActor
class VM {
    @concurrent
    func heavyDecode() async throws -> Data { ... }
 }
  1. 禁止重复标注隔离

    以下全部编译错误:

swift 复制代码
   @concurrent @MainActor func f() {}          // ❌ 冲突
   @concurrent nonisolated(nonsending) func f() {} // ❌ 冲突

实战:把 JSON 解码 offload 到后台

  1. 默认行为(主线程解码)
swift 复制代码
class Networking {
    func getFeed() async throws -> Feed {
        let data = try await loadData(from: .endpoint)
        return try await decode(data)   // 主线程执行
    }
    
    func decode<T: Decodable>(_ data: Data) async throws -> T {
        try JSONDecoder().decode(T.self, from: data)
    }
}

问题:下载 5 MB JSON → 主线程卡顿 300 ms。

  1. 精准外派
swift 复制代码
class Networking {
    // ... loadData 保持主线程
    
    @concurrent
    func decode<T: Decodable>(_ data: Data) async throws -> T {
        try JSONDecoder().decode(T.self, from: data) // 后台全局执行器
    }
}

→ 主线程利用率从 300 ms → 0 ms,解码期间用户可滚动界面。

  1. 调用方代码零改动
swift 复制代码
let feed: Feed = try await networking.getFeed()

外层依旧在主线程,@concurrent 仅影响方法内部隔离域。

使用场景 checklist

✅ 推荐

  • CPU 密集且与调用者隔离无关的工作:JSON / protobuf / ML 解码、大矩阵计算、图片编解码
  • 需要显式表达"我要后台"的 API,提高可读性

❌ 避免

  • 网络请求等本身已挂起的操作:

    URLSession.shared.data(for:) 内部已切后台,再加 @concurrent 无意义

  • 小成本计算(< 1 ms):线程切换开销反而更大

与 Swift 6 Feature Flag 的关系

Swift 6.2 提供编译器标志

-enable-upcoming-feature NonIsolatedNonSendingByDefault

开启后:所有 nonisolated 默认 = nonisolated(nonsending)。

此时

  • 留守调用者线程 → 默认
  • 外派全局执行器 → 必须显式 @concurrent

Swift6.2正式版默认开启,所以现在就把需要后台的函数标上 @concurrent,可提前兼容。

常见编译错误速查

错误提示 原因 修复方法
@concurrentcannot be applied to isolated function @MainActor/actor冲突 去掉重复标注
Concurrent function requires Sendable parameter 新隔离域要求数据线程安全 让参数遵守 Sendable
Call to main actor-isolated property from concurrent function 后台函数访问主线程属性 await MainActor.run { }或用 nonisolated属性

决策树:一眼选对

scss 复制代码
需要后台线程? ── 否 ──→ 用默认(或 nonisolated(nonsending))
       │
       是
       ▼
数据是 Sendable? ── 否 ──→ 先让数据 Sendable 或留在原线程
       │
       是
       ▼
标 @concurrent,享受全局执行器

总结:一句话背下来

默认留守主线程,真需后台再 @concurrent

  • 它不是并发万能钥匙,而是"精准外派"的显式声明
  • 未来开启 NonIsolatedNonSendingByDefault 后,@concurrent 将成为唯一通往全局执行器的官方入口
  • 现在就把重计算函数标好,提前平滑迁移 Swift 6.2

用好这个新属性,让主线程只负责 UI,把重活统统丢给后台------既省电,又顺滑。

相关推荐
HarderCoder21 分钟前
Swift 闭包(Closure)从入门到深入:语法、捕获与实战
swift
HarderCoder7 小时前
Swift 集合类型详解(三):自定义集合、持久化结构与 ORM 共舞
swift
HarderCoder7 小时前
Swift 集合类型详解(一):Array、Set、Dictionary 全貌与选型思路
swift
HarderCoder7 小时前
Swift 集合类型详解(二):自定义 Hashable、值语义与性能陷阱
swift
东坡肘子12 小时前
Sora 2:好模型,但未必是好生意 | 肘子的 Swift 周报 #0105
人工智能·swiftui·swift
HarderCoder1 天前
Swift 6 并发深渊:@unchecked Sendable 与“隐式 MainActor”如何合谋杀死你的 App
swiftui·swift
HarderCoder1 天前
告别 UIKit 生命周期:SwiftUI 视图一生全解析——从 init 到 deinit 的“隐秘角落”
swiftui·swift
HarderCoder1 天前
Swift 中的基本运算符:从加减乘除到逻辑与或非
ios·swift
HarderCoder1 天前
Swift 中“特性开关”实战笔记——用编译条件+EnvironmentValues优雅管理Debug/TestFlight/AppStore三环境
ios·swift
HarderCoder1 天前
Swift 并发任务中到底该不该用 `[weak self]`?—— 从原理到实战一次讲透
ios·swift