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

楔子

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

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

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

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

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

  1. @concurrent:破界而行的旁门秘籍
  2. 何时启用 NonisolatedNonsendingByDefault?非必要不增乱
  3. defaultIsolation:主 Actor 门派的包级新规矩
  4. defaultIsolation 实战:隐式标注,逆势需明
  5. 逆势而行:如何脱离主 Actor?
  6. 该不该启用 defaultIsolation?利弊权衡,因地制宜

今日列位武林好汉且随老夫执剑开卷,一探这两门新功究竟有何玄妙,又将如何改写 Swift 并发的江湖格局吧。

Let's go!!!;)


6. @concurrent:破界而行的旁门秘籍

与 "nonisolated (nonsending) " 一同出现的,还有 "@concurrent" 这枚破界令牌。

有了这枚令牌,函数的行为就能和 Swift 6.1 旧制一样 ------ 脱离调用者的经脉,建立新的隔离语境(专属经脉)。

请看示例:

swift 复制代码
@MainActor
class NetworkingClient {
    @concurrent
    nonisolated func loadUserPhotos() async throws -> [Photo] {
        return [Photo()]
    }
}

给函数标注@concurrent,就如同立下誓言:必定脱离调用者的 Actor,创建专属的隔离语境。

这枚令牌有一条铁律:只能用于 "nonisolated" 函数。如果将其用于 Actor 门派的招式,除非该招式明确标注 "nonisolated",否则就是违规练功:

swift 复制代码
actor SomeGenerator {
    // 此为禁忌,不可如此
    @concurrent
    func randomID() async throws -> UUID {
        return UUID()
    }

    // 此为正道,可如此行
    @concurrent
    nonisolated func randomID() async throws -> UUID {
        return UUID()
    }
}

需要注意的是,在撰写本文时,这两种写法暂时都能运行,而且未标注 "nonisolated" 的@concurrent函数在运行时似乎没有隔离 ------ 但根据《SE-0461 秘籍》的规定,这是 Swift 6.2 工具链的一个临时疏漏,日后定会修正,大家千万不要效仿前者,以免走火入魔。

7. 何时启用 NonisolatedNonsendingByDefault?非必要不增乱

在老夫看来,启用 "NonisolatedNonsendingByDefault" 令牌实乃明智之举。

它带来了一种新的工作方式:nonisolated 异步函数会跟随调用者的 Actor 运行,而非在独立的隔离语境中运行。实际上,这能减少很多编译器错误,而且根据本人的尝试,还能省去不少主 Actor 标注。

老夫一直主张减少应用中的并发量,只在确实需要时才引入 ------ 这一令牌恰好能帮助宝子们实现这一点。在决定给应用中的所有内容都标注@concurrent以确保安全之前,请先问问自己是否真的有此必要。

其实,很可能根本没必要,而且从整体来看,并非所有代码都并发运行,这会让代码及其执行过程更容易理解。

当同时采用 Swift 6.2 的第二项主要特性 "defaultIsolation" 时,情况更是如此。

8. defaultIsolation:主 Actor 门派的包级新规矩

Swift 6.2 的另一大绝招 "defaultIsolation",堪称主 Actor 门派的包级(package)新规。

在 Swift 6.1 中,代码只有在被@MainActor标注(或遵循标有@MainActor的协议)时,才会归入主 Actor------ 就像只有加入主 Actor 门派,才能学习主经脉的功法一样。

给代码标注@MainActor是解决编译器错误的常用方法,而且大多数情况下都是正确的做法。并非所有代码都需要在后台线程异步执行。那样做成本相对较高,往往对性能没有提升,还会让代码难以理解。

在采用 Swift 并发之前,各位秃头少侠们不会到处使用,那样做成本相对较高,往往对性能没有提升,反而还会让代码难以理解。

在采用 Swift 并发之前,小伙伴们不会到处使用DispatchQueue.global(),那现在又何必做类似的事情呢?

言归正传,在 Swift 6.2 中,我们可以在包级别(package)设置默认在主 Actor 上运行代码。这是《SE-0466 秘籍》引入的特性。

这意味着,UI 包、应用目标和模型包等,都能自动在主 Actor 上运行代码,除非通过@concurrent或自定义 Actor 明确选择不在主 Actor 上运行。

要启用这一特性,可在swiftSettings中设置defaultIsolation,或作为编译器参数传入:

swift 复制代码
swiftSettings: [
    .defaultIsolation(MainActor.self),
    .enableExperimentalFeature("NonisolatedNonsendingByDefault")
]

不必将defaultIsolationNonisolatedNonsendingByDefault一起使用,但在老夫的实验中,同时使用这两个选项效果很好。

9. defaultIsolation 实战:隐式标注,逆势需明

目前,可以将MainActor.self作为默认隔离传入,使所有代码默认在主 Actor 上运行;也可以传入nil以保持现有行为(或者根本不传入该设置,同样保持现有行为)。

启用这一特性后,Swift 会推断所有对象都带有@MainActor标注,除非明确指定其他情况:

swift 复制代码
@Observable
class Person {
    var myValue: Int = 0
    let obj = TestClass()

    // 如果 defaultIsolation 设置为主 Actor,此函数始终在主 Actor 上运行
    func runMeSomewhere() async {
        MainActor.assertIsolated()
        // 执行一些操作、调用异步函数等
    }
}

这段代码包含一个 nonisolated 异步函数。这意味着,默认情况下,它会继承调用runMeSomewhere的 Actor。如果从主 Actor 调用,它就在主 Actor 上运行;如果从另一个 Actor 或非 Actor 处调用,它就不在主 Actor 上运行。

这很可能完全不是预期的结果。

或许我们编写异步函数只是为了能调用其他需要等待的函数。如果runMeSomewhere不执行任何繁重的处理,我们可能希望Person在主 Actor 上。它是一个可观察类,很可能用于驱动 UI,这意味着几乎所有对该对象的访问都应该在主 Actor 上进行。

defaultIsolation设置为MainActor.self时,Person会被隐式标注@MainActor,因此Person的所有工作都在主 Actor 上运行。

10. 逆势而行:如何脱离主 Actor?

假设我们想给Person添加一个不在主 Actor 上运行的函数。可以像往常一样使用 nonisolated:

swift 复制代码
// 此函数将在调用者的 Actor 上运行
nonisolated func runMeSomewhere() async {
    MainActor.assertIsolated()
    // 执行一些操作、调用异步函数等
}

如果想确保函数绝对不在主 Actor 上运行则可以这样做:

swift 复制代码
// 此函数将在调用者的 Actor 上运行
@concurrent
nonisolated func runMeSomewhere() async {
    MainActor.assertIsolated()
    // 执行一些操作、调用异步函数等
}

对于每个想要设置为 nonisolated 的函数或属性,都需要明确选择不使用主 Actor 推断;不能为整个类型进行这样的设置。

当然,自定义的 Actor 不会突然开始在主 Actor 上运行,而且标注了其它全局 Actor 的类型也不会受到这一变化的影响。

11. 该不该启用 defaultIsolation?利弊权衡,因地制宜

这个问题很难回答。老夫的初步想法是 "应该启用"。

对于应用目标、UI 包和主要存放视图模型的包,默认在主 Actor 上运行无疑是正确的选择。

不过,列位仍然可以在需要的地方引入并发,而且会比以往更具目的性。

那么本篇的内容就要告一段落了!大家学"废"了吗?

最后,感谢大家的观看,再会啦!8-)

相关推荐
YungFan32 分钟前
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_Coder10 小时前
iOS Widget 开发-1:什么是 iOS Widget?开发前的基本认知
ios·swiftui·swift·widget
小弟调调1 天前
Vidwall: 支持将 4K 视频设置为动态桌面壁纸,兼容 MP4 和 MOV 格式
macos·swiftui·桌面应用·macos app