
楔子
江湖风云变幻,Swift 武林近日再掀波澜。
传闻 Apple 于密室推演三月,终得《Swift 6.2 并发新篇》,扬言要破解困扰开发者多年的 "经脉错乱" 之症 ------ 那便是异步函数与同步函数运行规则不一、主 Actor 调用常生冲突之陈年旧疾。

想当年,多少英雄好汉折戟于 GCD 到 Swift 并发的转型之路:明明是同门函数,同步者循调用者经脉而行,异步者却偏要另辟蹊径,轻则编译器怒目相向,重则数据走火入魔。
如今 6.2 版本携 "nonisolated (nonsending) " 与 "defaultIsolation" 两大神功而来,声称要让代码运行如行云流水,再无经脉冲突之虞。
在本篇博文,您将学到如下内容:
- 开篇明义:Swift 并发的 "武功瓶颈" 与 6.2 新突破
- nonisolated (nonsending) 新功揭秘:SE-0461 秘籍的前世今生
- 旧制之弊:Swift 6.1 前的 "nonisolated" 暗伤
- 冲突根源:双脉并行的 "数据争夺" 危机
- nonisolated (nonsending) 新功详解:经脉随行,边界不破
今日列位武林好汉且随老夫执剑开卷,一探这两门新功究竟有何玄妙,又将如何改写 Swift 并发的江湖格局吧。
Let's go!!!;)
1. 开篇明义:Swift 并发的 "武功瓶颈" 与 6.2 新突破
江湖之中,Swift 并发之道向来以高深难悟著称,其心法与昔日 GCD 的招式路数截然不同。
许多开发者初涉此域,常因概念繁杂而倍感困惑。Apple 早已洞察这一弊端,在《武林愿景》中宣告:Swift 6.2 将对并发体系进行革新 ------ 并非撼动根基,而是调整招式默认的发力点(即代码默认的运行位置)。

今日,且随老夫一同探寻两大核心变革:一是 "nonisolated (nonsending) 默认令牌 " 这门新功,二是借助 "defaultIsolation" 设置让代码默认归入 "主 Actor 门派" 的新规矩。
通读此文后,各位定能明晰 Swift 6.2 对代码的深远影响,以及在未来 Xcode 正式收录该版本前应如何做好准备。
2. nonisolated (nonsending) 新功揭秘:SE-0461 秘籍的前世今生
"nonisolated (nonsending) " 这门新功,源自《SE-0461 秘籍》,堪称 Swift 并发体系的一次脱胎换骨的变革。在撰写本文时,它仍被封印在 "NonisolatedNonsendingByDefault" 这枚功法令牌之后 ------ 若要启用此令牌,使用 SPM 包的开发者可参考《SPM 新功启用要诀》,使用 Xcode 的开发者则需查阅《Xcode 新功解锁指南》。
老夫在此次演练中使用的是 SPM 包,因此在 Package.swift 中设置了这样的门规:
swift
.executableTarget(
name: "SwiftChanges",
swiftSettings: [
.enableExperimentalFeature("NonisolatedNonsendingByDefault")
]
)
话不多说,先来解析这门新功的来龙去脉:它究竟是什么路数?能解决什么武学瓶颈?又会如何彻底改变代码的运行方式呢?
3. 旧制之弊:Swift 6.1 前的 "nonisolated" 暗伤
在 Swift 6.1 及更早版本中,"nonisolated" 异步函数存在一处暗伤。请看这样的招式:
swift
class NetworkingClient {
func loadUserPhotos() async throws -> [Photo] {
// ...
}
}
当调用loadUserPhotos
时,这一招式绝不会进入任何 "Actor 经脉"(即远离主线程)。因为它既是 "nonisolated"(非隔离)的,又是异步函数,就像一门脱离主经脉的旁支功法,天生与主经脉格格不入。
这种特性往往会引发内力冲突。请看下面的代码:
swift
struct SomeView: View {
let network = NetworkingClient()
var body: some View {
Text("Hello, world")
.task { await getData() }
}
func getData() async {
do {
// 传递'self.network'恐生数据冲突
let photos = try await network.loadUserPhotos()
} catch {
// ...
}
}
}
编译器见此情景定会厉声呵斥:"将主 Actor 隔离的'self.network'传入 nonisolated 方法,可能导致'非隔离'与'主 Actor 隔离'操作之间产生'数据 race'(内力冲撞)!" 。

这种错误就如同主经脉的内力流入旁支,极易造成气血紊乱。
4. 冲突根源:双脉并行的 "数据争夺" 危机
上面这段代码的问题在于:loadUserPhotos
运行在自身的隔离语境(独立经脉)中,与主 Actor 的运功节奏并行。
由于NetworkingClient
实例由主 Actor 创建并掌控,主 Actor 可以在loadUserPhotos
运行时对其进行读写操作;而该函数又能访问self
,这就意味着两条经脉可能同时操作同一个NetworkingClient
实例 ------ 如果该实例不具备 "Sendable"(可安全传递)特性,这种双脉并行的情况必然会导致数据 race(内力互冲)。

更让人困扰的是:同样是 "nonisolated",同步函数和异步函数的运功路数却大相径庭。
同步函数总会跟随调用者的 Actor 运行;而如果从主 Actor 调用 nonisolated 异步函数,该函数不会在主 Actor 上运行;即便从非 Actor 处调用 nonisolated 异步函数,它也不会在主 Actor 上运行。
看以下注解示例便可知晓:
swift
// 此招必随调用者经脉运行
nonisolated func nonIsolatedSync() {}
// 此招属某 Actor(此处为主 Actor)专属,必在其经脉运行
@MainActor func isolatedSync() {}
// 此招绝不入任何 Actor,只在后台经脉运行
nonisolated func nonIsolatedAsync() async {}
// 此招属某 Actor(此处为主 Actor)专属,必在其经脉运行
@MainActor func isolatedAsync() async {}
可见,异步函数和同步函数的行为差异显著,尤其是 nonisolated 异步函数与 nonisolated 同步函数之间,更是界限分明。
Swift 6.2 将以 "nonisolated (nonsending)" 作为新的默认规则,消除这种武学乱象,使异步函数和同步函数的行为保持一致。
5. nonisolated (nonsending) 新功详解:经脉随行,边界不破
Swift 6.1 及更早版本中函数行为的不一致,常常让开发者感到困惑。
因此在 Swift 6.2 中,异步函数将采用 "nonisolated (nonsending) " 作为 nonisolated 函数的新默认规则。开发者无需手动标注,所有 nonisolated 异步函数都会遵循这一规则,除非另有说明。
这门新功的精妙之处在于:标有 "nonisolated (nonsending)" 的函数不会跨越 Actor 边界(不突破经脉壁垒);通俗地说,就是会跟随调用者的经脉运行。
因此,启用 "NonisolatedNonsendingByDefault " 令牌后,前文出现错误的代码便能正常运行:loadUserPhotos
默认属于 "nonisolated (nonsending)",其运行会在主 Actor 经脉上,而非协同线程池(旁支经脉)。
且看以下几个示例:
swift
class NetworkingClient {
func loadUserPhotos() async throws -> [Photo] {
// ...
}
}
这里的loadUserPhotos
既是 nonisolated 又是异步函数,所以默认遵循 "nonisolated (nonsending)" 规则,它会跟随调用者的 Actor(如果有的话)运行。
也就是说:从主 Actor 调用,它就在主 Actor 上运行;从非 Actor 处调用,它就进入旁支经脉运行。
若给NetworkingClient
加上@MainActor
标注,就如同将其归入主 Actor 门派一般:
swift
@MainActor
class NetworkingClient {
func loadUserPhotos() async throws -> [Photo] {
return [Photo()]
}
}
这样一来,loadUserPhotos
就成为主 Actor 的专属招式,无论从何处调用,都会在主 Actor 经脉上运行。
再看主 Actor 门派中,某一招式明确标注 "nonisolated" 的情况:
swift
@MainActor
class NetworkingClient {
nonisolated func loadUserPhotos() async throws -> [Photo] {
return [Photo()]
}
}
此时,即便没有手动标注 "nonisolated (nonsending) ",新的默认规则依然生效:NetworkingClient
属于主 Actor 门派,但loadUserPhotos
跳出了门派限制,会跟随调用者的经脉运行 ------ 从主 Actor 调用,就在主 Actor 上运行;从其他地方调用,就在相应的地方运行。
然而,要是想让某一招式绝对不在主 Actor 经脉上运行,该怎么做呢?前面提到的方法,要么让loadUserPhotos
隶属于主 Actor,要么让它跟随调用者的 Actor,它们都无法实现这一需求,在下一篇中,列为秃头少侠们且听我继续讲解。
我们不见不散!别走远,马上回来!8-)