背景:为什么突然冒出 @concurrent
?
Swift 6.2 引入了两项默认行为变化:
-
Main Actor 默认隔离(UI相关的Target或package)
未显式标注隔离的代码自动视为
@MainActor
。

-
nonisolated 函数行为分裂
async
→ 跑在全局执行器(后台线程)- 非
async
→ 跑在调用者执行器(可能主线程)
这让"同一段 nonisolated 代码"在不同签名下表现不一致,容易踩坑。
于是 Swift 团队先推出 nonisolated(nonsending)
统一行为(永远留在调用者线程 ),再用 @concurrent
显式声明"我要 offload 到全局执行器"。
三条隔离路线一览
写法 | 跑在哪 | 引入新隔离域 | 需 Sendable |
---|---|---|---|
nonisolated + async(老模式) |
全局执行器 | ✅ | ✅ |
nonisolated(nonsending) |
调用者执行器 | ❌ | ❌ |
@concurrent |
全局执行器 | ✅ | ✅ |
口诀:"nonsending 留守,@concurrent 外派。"
语法与位置
-
自动隐式 nonisolated
写
@concurrent
即可,不必再加nonisolated
:
swift
@concurrent
func decode<T: Decodable>(_ data: Data) async throws -> T { ... }
-
可放在类型内
即使类型本身是
@MainActor
或actor
,也能外派单个方法:
swift
@MainActor
class VM {
@concurrent
func heavyDecode() async throws -> Data { ... }
}
-
禁止重复标注隔离
以下全部编译错误:
swift
@concurrent @MainActor func f() {} // ❌ 冲突
@concurrent nonisolated(nonsending) func f() {} // ❌ 冲突
实战:把 JSON 解码 offload 到后台
- 默认行为(主线程解码)
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。
- 精准外派
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,解码期间用户可滚动界面。
- 调用方代码零改动
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
,可提前兼容。

常见编译错误速查
错误提示 | 原因 | 修复方法 |
---|---|---|
@concurrent cannot 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,把重活统统丢给后台------既省电,又顺滑。