WWDC Quick Look 💓 By SwiftGGTeam
What’s new in Swift

What’s new in Swift

观看原视频

Highlight

Swift 6.2 把并发模型从「默认并发」转向「默认单线程」,新增 InlineArraySpanSubprocessObservations 等基础库,并让 macros 项目通过预编译 swift-syntax 大幅缩短 clean build 时间。


核心内容

写过一段时间 Swift 6 并发代码的人,多半都被同一类报错困住过。给一个普通的 class 加个 async 方法,编译器立刻警告它跨过了 main actor 边界;想让一个 @MainActor 类型实现某个普通协议,编译器又说协议实现可能跨线程。一个 App 最常见的场景——UI 类型、单例、回调——都被并发安全检查截了一刀。开发者要么到处贴 @MainActor,要么把代码改造成自己也不太理解的样子。

Swift 6.2 把这件事重新想了一遍。新的默认是:async 函数在调用者所在的 actor 上执行;@MainActor 可以作为模块默认推断模式开启;协议实现可以显式标注为 @MainActor Exportable,让 main actor 类型也能合规地遵守协议。一个完全跑在主线程的 App,写起来又像 Swift 5 时代那样直观,但仍然保留了 Swift 6 的数据竞争检查。需要把 CPU 密集任务卸载到后台时,用 @concurrent 显式标注一次,意图清晰、性能可预期。

除了并发,6.2 还重写了很多日常 API:NotificationCenter 给通知赋予具体类型、消除字典取值和强转;新的 Subprocess 包让脚本场景一行 await run(.name("pwd")) 就能拉起进程;Observations@Observable 的状态变化包成 AsyncSequence;Swift Testing 加入 Attachment 与 exit tests。工具链层面,swiftly 1.0 在 macOS 上线,pre-built swift-syntax 让 macros 项目 clean build 时间动辄少几分钟,Explicitly Built Modules 让 LLDB 第一次执行 p/po 不再卡顿。


详细内容

新的 Subprocess 包09:44)。Foundation Workgroup 把脚本场景下最常见的「拉起子进程」抽出来做成独立 package:

import Subprocess

let swiftPath = FilePath("/usr/bin/swift")
let result = try await run(
  .path(swiftPath),
  arguments: ["--version"]
)

let swiftVersion = result.standardOutput

关键点:

  • import Subprocess:来自独立的 swift-subprocess 包,目前 0.1 版本,等待 1.0 前的反馈。
  • .path(swiftPath):用 FilePath 指定可执行文件的完整路径;如果给字符串则按 $PATH 查找。
  • try await run(...):返回的 result 包含 exit status、standardOutputstandardError 等信息,和 async/await 模型自然衔接。

NotificationCenter 用上具体类型11:34)。过去观察键盘通知要传 UIResponder.keyboardWillShowNotification 字符串、从 userInfo 字典里挖 CGRect、还得处理 main actor 隔离。新 API 把这些步骤合并成一行:

import UIKit

@MainActor
class KeyboardObserver {
  func registerObserver(screen: UIScreen) {
    let center = NotificationCenter.default
    let token = center.addObserver(
      of: screen,
      for: .keyboardWillShow
    ) { keyboardState in
      let startFrame = keyboardState.startFrame
      let endFrame = keyboardState.endFrame

      self.keyboardWillShow(startFrame: startFrame, endFrame: endFrame)
    }
  }

  func keyboardWillShow(startFrame: CGRect, endFrame: CGRect) {}
}

关键点:

  • for: .keyboardWillShow:编译期检查 UIScreen 是否真的会发这个通知,写错通知名直接编译失败。
  • keyboardState.startFrame:payload 是强类型结构体,不再需要字典 key 加 as? CGRect
  • 通知声明为 NotificationCenter.MainActorMessage,回调直接处于 main actor,无需额外 MainActor.assumeIsolated

Isolated conformances 解决协议跨界问题33:04)。@MainActor 类型实现普通协议过去会报 “crosses into main actor-isolated code” 错误。Swift 6.2 允许在协议遵守上写 actor 注解:

protocol Exportable {
  func export()
}

extension StickerModel: @MainActor Exportable {
  func export() {
    photoProcessor.exportAsPNG()
  }
}

关键点:

  • @MainActor Exportable:声明这个 conformance 只能在 main actor 上下文里被使用。
  • 调用方必须自身也运行在 main actor 上,否则编译器报错,把跨界检查从「禁止遵守」放宽成「使用时检查」。
  • 配合 @MainActor 模块默认推断模式,单线程 App 几乎不再需要手写 @MainActor 标注。

显式卸载到后台35:06)。新模型下 async 默认跑在调用者 actor,要切到全局并发执行器需要显式标注:

class PhotoProcessor {
  var cachedStickers: [String: Sticker]

  func extractSticker(data: Data, with id: String) async -> Sticker {
      if let sticker = cachedStickers[id] {
        return sticker
      }

      let sticker = await Self.extractSubject(from: data)
      cachedStickers[id] = sticker
      return sticker
  }

  @concurrent
  static func extractSubject(from data: Data) async -> Sticker {}
}

关键点:

  • extractSticker 没有 @concurrent,所以跑在调用者的 actor 上,缓存读写天然线程安全。
  • @concurrent static func extractSubject:被标记为可并发执行,离开 main actor 跑在全局执行器。
  • 把 CPU 密集任务的 hop 集中在一处,性能调优时只需要审视少数 @concurrent 函数。

Observations 把状态变化变成 AsyncSequence14:05):

let player = Player(name: "Holly")
let values = Observations {
  let score = "\(player.score) points"
  let item =
    switch player.item {
    case .none: "no item"
    case .banana: "a banana"
    case .star: "a star"
    }
  return "\(score) and \(item)"
}

player.score += 2
player.item = .banana

for await value in values { print(value) }

关键点:

  • Observations { ... }:闭包内访问的所有 @Observable 属性都会被自动追踪。
  • 同一 tick 里多次写入(score += 2item = .banana)会合并成一次事务化更新,避免中间态被消费。
  • for await value in values:把 UI 状态以 AsyncSequence 形式驱动,自然接入 SwiftUI 之外的异步循环。

核心启发

1. 在 Xcode build settings 中打开 @MainActor 默认推断模式

为什么值得做:单线程 UI App 是绝大多数业务代码的形态,开启该模式后省下满屏的 @MainActor 标注,代码更接近 Swift 5 的可读性,同时仍保留 Swift 6 数据竞争检查。

怎么开始:在 SwiftPM manifest 里通过 SwiftSettings API 一次性开启,避免逐文件改;新工程直接默认开启。

2. 把性能关键路径上的 Array 替换为 InlineArray

为什么值得做:固定大小的数据(颜色矩阵、查找表、网格状态)用 InlineArray 直接内联存储在栈上,省掉堆分配和引用计数,渲染、解码、信号处理路径上常能拿到可观收益。

怎么开始:先用 Instruments 定位 hot path 的内存分配热点,再逐个替换,配合 Span 提供安全的零拷贝视图。

3. 把 NotificationCenter 观察迁移到具体类型 API

为什么值得做:消除「通知名拼错」「userInfo key 拼错」「main actor 跨界」三类常见 bug,调用点行数减半,类型即文档。

怎么开始:从 UIKit 键盘、应用生命周期等 Apple 已迁移的通知开始;自定义通知用 MainActorMessage / AsyncMessage 协议加上具体 payload 类型。

4. 给 macros 项目接上 pre-built swift-syntax

为什么值得做:CI 上每次 clean build 省几分钟,本地 Xcode 首次构建也明显加速;macro 作者发布 tagged release 后下游用户无需改动即可受益。

怎么开始:升级到 Xcode 26 / Swift 6.2,将 macro 包依赖锁定到 swift-syntax 的 tagged release,验证 build log 中是否命中预编译产物。

5. 用 @concurrent 替代散落的 Task.detached

为什么值得做:把「这段任务要离开当前 actor」的意图写在函数签名上而非调用点,更适合 review,也让性能分析有清晰的边界。

怎么开始:审视现有 Task.detachedDispatchQueue.global().async 调用,能用静态函数表达的就改成 @concurrent,保留必须运行时调度的少数场景。


关联 Session

评论

GitHub Issues · utterances