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")
]

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

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-)

相关推荐
传奇开心果编程12 小时前
【现代声明式UI学与练】第1课 从命令式UI到声明式UI
学习·flutter·ui·swiftui·react·android jetpack
传奇开心果编程1 天前
【SwiftUI提高练中学】第13课 实时活动与灵动岛:ActivityKit 实战
学习·ui·ios·swiftui·swift
AC赳赳老秦2 天前
数据采集全链路审计留痕:用 OpenClaw 实现合规审计与追溯
开发语言·汇编·python·php·swift·deepseek·openclaw
传奇开心果编程2 天前
【SwiftUI入门练中学】第17课 通知与后台任务
学习·ui·ios·swiftui·swift
逐流人3 天前
Ceph对象存储与文件系统存储管理:从RADOS网关、S3与Swift到CephFS快照同步
linux·运维·ceph·云原生·云计算·swift·rados
传奇开心果编程3 天前
【SwiftUI入门练中学】第7课 动画与手势:让界面更灵动
学习·macos·ui·ios·swiftui·swift
传奇开心果编程4 天前
【SwiftUI娓娓道来】第3课:让界面活起来——交互与动画指南
学习·macos·ui·ios·swiftui·swift
用户38034165882975 天前
控制中心一键后台录音:我被自己的 Spike 骗了两次
ios·swift