Swift 6.2 并发江湖:两大神功破局旧制,代码运行经脉革新(上)

楔子

江湖风云变幻,Swift 武林近日再掀波澜。

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

想当年,多少英雄好汉折戟于 GCD 到 Swift 并发的转型之路:明明是同门函数,同步者循调用者经脉而行,异步者却偏要另辟蹊径,轻则编译器怒目相向,重则数据走火入魔。

如今 6.2 版本携 "nonisolated (nonsending) " 与 "defaultIsolation" 两大神功而来,声称要让代码运行如行云流水,再无经脉冲突之虞。

在本篇博文,您将学到如下内容:

  1. 开篇明义:Swift 并发的 "武功瓶颈" 与 6.2 新突破
  2. nonisolated (nonsending) 新功揭秘:SE-0461 秘籍的前世今生
  3. 旧制之弊:Swift 6.1 前的 "nonisolated" 暗伤
  4. 冲突根源:双脉并行的 "数据争夺" 危机
  5. 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-)

相关推荐
YungFan42 分钟前
iOS26适配指南之UINavigationController
ios·swift
Swift社区2 小时前
Swift 解 LeetCode 321:拼接两个数组中的最大数,贪心 + 合并全解析
开发语言·leetcode·swift
大熊猫侯佩10 小时前
Swift 数学计算:用 Accelerate 框架让性能“加速吃鸡”
算法·swift
大熊猫侯佩10 小时前
Swift 6.2 并发江湖:两大神功破局旧制,代码运行经脉革新(下)
swiftui·swift·wwdc
大熊猫侯佩10 小时前
SwiftUI 7 江湖新风:WWDC25 揭晓神秘武林志
swiftui·swift·wwdc
大熊猫侯佩10 小时前
SwiftUI 7(iOS 26 / iPadOS 26)中玻璃化标签页的全新玩法
swiftui·swift·apple
Daniel_Coder11 小时前
iOS Widget 开发-1:什么是 iOS Widget?开发前的基本认知
ios·swiftui·swift·widget
小弟调调1 天前
Vidwall: 支持将 4K 视频设置为动态桌面壁纸,兼容 MP4 和 MOV 格式
macos·swiftui·桌面应用·macos app