Xcode 26 中 nonisolated(sending) 与默认隔离设置冲突吗?
两项设置不冲突,两个函数仍是 @MainActor
当 Default Actor Isolation 设置为 MainActor 时,题目中两个没有显式隔离标注的全局函数都会被推断为 @MainActor。同时启用 nonisolated(nonsending) by Default,也不会把它们改成 nonisolated。
这里需要纠正一下设置名称:应为 nonisolated(nonsending) by Default,其中是 nonsending,不是 sending。它对应 Swift 的 NonisolatedNonsendingByDefault 特性,控制非隔离异步函数的默认执行方式。Swift 官方说明
同时启用这两项设置后,题目中的代码在隔离语义上相当于:
@MainActor
func syncMethod() {
// 可以直接访问 MainActor 隔离的状态
}
@MainActor
func asyncMethod() async {
// 可以直接访问 MainActor 隔离的状态
}
默认隔离设置不会改变函数的同步或异步性质,第二个函数仍保留 async。
两项设置分别管什么
Default Actor Isolation = MainActor 决定未标注声明的默认 actor 隔离。题目中的全局函数没有其他隔离推断来源,因此同步函数和异步函数都会被推断为 @MainActor。显式声明为 nonisolated 可以退出这项默认规则。SE-0466
nonisolated(nonsending) by Default 作用于已经是 nonisolated 的异步函数:
- 关闭时,沿用此前的执行语义,在不关联任何 actor 的 generic executor 上执行。
- 开启时,默认沿用调用方的 actor 执行;需要显式离开 actor 时,可以使用
@concurrent。
这项设置不会自动给所有函数添加 nonisolated,也不改变同步函数的执行规则。SE-0461
| 默认隔离设置 | nonisolated(nonsending) by Default | syncMethod() | asyncMethod() |
|---|---|---|---|
nonisolated | 关闭 | 非隔离,同步执行 | 非隔离,在 generic executor 上执行 |
nonisolated | 开启 | 非隔离,同步执行 | 非隔离,沿用调用方的 actor |
MainActor | 关闭或开启 | @MainActor | @MainActor |
如何按预期声明函数
保持主 actor 隔离
如果函数需要直接读取或修改主 actor 状态,保留当前配置即可。也可以显式写出 @MainActor,让隔离要求不依赖项目默认值:
@MainActor
var count = 0
@MainActor
func syncMethod() {
count += 1
}
@MainActor
func asyncMethod() async {
count += 1
}
退出默认主 actor 隔离
如果函数需要是非隔离的,显式添加 nonisolated:
nonisolated
func syncMethod() {
// 同步执行,不要求调用方位于某个特定 actor
}
nonisolated
func asyncMethod() async {
// 启用 NonisolatedNonsendingByDefault 时,
// 默认沿用调用方的 actor 执行
}
异步函数也可以直接写出所需的执行语义:
nonisolated(nonsending)
func asyncMethod() async {
// 沿用调用方的 actor 执行
}
在支持该语法的 Swift 6.2 编译器中,这种显式写法不受 NonisolatedNonsendingByDefault 是否开启的影响。Swift 官方说明
明确让异步计算离开 actor
要让耗时计算离开调用方的 actor,可以使用 @concurrent:
@concurrent
func calculateTotal(_ values: [Int]) async -> Int {
values.reduce(0, +)
}
@MainActor
func updateTotal() async {
let total = await calculateTotal([10, 20, 30])
// 返回后继续在 MainActor 上执行
print(total)
}
@concurrent 隐含 nonisolated,可以退出默认主 actor 隔离。它不能与显式 @MainActor 同时用于同一个函数,也不能用于同步函数。SE-0461
容易混淆的地方
“沿用调用方的 actor 执行”和“声明为 @MainActor”有区别。nonisolated(nonsending) 函数可以由不同 actor 调用,不能因为某次调用来自主 actor,就在函数体中直接访问任意 @MainActor 隔离状态。@MainActor 则是声明本身的隔离约束。
async 也不保证在后台执行。启用新规则后,从主 actor 调用的非隔离异步函数可能继续占用主 actor,函数中的长时间同步计算仍可能影响界面响应。
题目对旧行为的描述需要限定前提:按照 Swift 5.7 的 SE-0338 规则,普通非隔离异步函数在 generic executor 上执行。可以用“切到 cooperative thread pool”理解常见运行情况,但判断代码是否安全时,应以 actor 隔离和 executor 语义为准,不能仅凭线程日志。SE-0338
备注:内容仅供参考。
