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

What's new in Swift

观看原视频

Highlight

Swift 5.9 引入了 if/switch 表达式、参数包(Parameter Packs)、Swift Macros、@Observable 宏、~Copyable 所有权控制、C++ 互操作和自定义 Actor Executor 等特性,让代码表达更简洁、泛型 API 更灵活、编译期生成代码更安全,同时把 Swift 的性能和安全性优势扩展到与 C++ 混合代码库和服务器端分布式程序中。

核心内容

if/switch 表达式简化赋值

以前你要根据条件给 let 变量赋值,只能用嵌套三元运算符或立即执行的闭包 hack。代码难看还容易出错。

03:06

Swift 5.9 允许 if/else 和 switch 直接作为表达式使用。初始化变量、属性、甚至返回语句都可以用熟悉的条件语句。

03:46

参数包终结重载爆炸

你有没有写过这样的代码:一个函数为了支持不同数量的参数,写了 6 个重载版本?每个版本除了参数数量不同,逻辑完全一样。更糟的是,第 7 个参数就会编译报错。

06:03

参数包让你写一个泛型函数就能处理任意数量的类型参数,同时保留每个参数的静态类型信息。调用方式和使用重载时完全一样。

07:36

Swift Macros 在编译期生成代码

宏是 Swift 5.9 最重磅的新特性。它允许在编译期根据你的代码生成新的代码,减少样板代码的同时保持类型安全。

10:01

比如 assert(a == b) 失败时,普通断言只告诉你”断言失败”,你不知道 a 和 b 的具体值。用宏实现的断言可以展开成捕获每个子表达式值的代码,失败时告诉你”a 是 10,b 是 17”。

11:02

@Observable 宏是 Apple 自己在框架中使用的例子。它把原本需要手动写的 ObservableObject + @Published + objectWillChange.send() 全部自动化,同时实现更细粒度的依赖追踪。

16:57

~Copyable 控制值类型复制

Swift 的值类型默认可以任意复制。这在大多数情况下是好事,但处理文件描述符、网络连接等资源时,复制会导致重复释放或资源泄漏。

23:59

~Copyable 让你声明一个值类型不可复制。配合 consuming 方法,编译器可以在编译期就阻止”使用已释放资源”的错误,而不是等到运行时崩溃。

26:06

C++ 互操作

Swift 5.9 实现了与 C++ 的双向互操作。你可以在 Swift 中直接调用 C++ 类的方法、访问属性、使用 STL 容器;也可以在 C++ 中调用 Swift 的函数、使用 Swift 的结构体。

28:52

这意味着大型 C++ 代码库可以逐步引入 Swift,而不需要一次性重写。

自定义 Actor Executor

Actor 的默认执行器是系统管理的串行队列。但当你需要把 Actor 与特定的调度队列(如 SQLite 的串行队列)绑定时,以前没有办法。

35:30

Swift 5.9 允许你自定义 Actor 的执行器。DispatchSerialQueue 已经内置了 SerialExecutor 协议的支持,你只需要在 Actor 中声明 unownedExecutor 即可。

35:58

详细内容

if 表达式替代三元运算符

// 以前:嵌套三元运算符难以阅读
let bullet =
    isRoot && (count == 0 || !willExpand) ? ""
        : count == 0    ? "- "
        : maxDepth <= 0 ? "▹ " : "▿ "

03:06

// Swift 5.9:用 if 表达式
let bullet =
    if isRoot && (count == 0 || !willExpand) { "" }
    else if count == 0 { "- " }
    else if maxDepth <= 0 { "▹ " }
    else { "▿ " }

03:19

// 以前:属性初始化需要闭包 hack
let attributedName = AttributedString(markdown: displayName)

03:30

// Swift 5.9:直接用 if 表达式初始化
let attributedName =
    if let displayName, !displayName.isEmpty {
        AttributedString(markdown: displayName)
    } else {
        "Untitled"
    }

03:46

关键点:

  • if/else 和 switch 现在可以在任何需要表达式的地方使用
  • 每个分支必须返回相同类型的值
  • 特别适合变量初始化、属性默认值、函数返回等场景
  • 比三元运算符嵌套可读性好得多

参数包替代重载

// 以前:为每个参数数量写重载
func evaluate<Result>(_ request: Request<Result>) -> Result
func evaluate<R1, R2>(_ r1: Request<R1>, _ r2: Request<R2>) -> (R1, R2)
func evaluate<R1, R2, R3>(_ r1: Request<R1>, _ r2: Request<R2>, _ r3: Request<R3>) -> (R1, R2, R3)
// ... 写到 6 个参数就停了

// 第 7 个参数编译报错:Extra argument in call
let results = evaluator.evaluate(r1, r2, r3, r4, r5, r6, r7)

06:35

// Swift 5.9:一个函数处理任意数量
func evaluate<each Result>(_: repeat Request<each Result>) -> (repeat each Result)

07:36

// 调用方式和以前一样
let value = RequestEvaluator().evaluate(request)
let (x, y) = RequestEvaluator().evaluate(r1, r2)
let (x, y, z) = RequestEvaluator().evaluate(r1, r2, r3)

08:21

关键点:

  • each Result 声明一个类型参数包
  • repeat Request<each Result> 展开为任意数量的参数
  • repeat each Result 在返回类型中展开为对应数量的元组元素
  • 调用语法和重载完全一致,用户无感知
  • 不再有”最多 N 个参数”的人为限制

Swift Macros 的基本用法

// 导入宏包
import PowerAssert

// 使用宏:失败时显示每个子表达式的值
#assert(max(a, b) == c)
// 输出:Assertion failed: max(a, b) == c
//        |   |  |  |  |
//        |   10 17 10 17
//        false

11:02

// 宏声明
@freestanding(expression)
public macro assert(_ condition: Bool)

// 宏定义:指向编译器插件
public macro assert(_ condition: Bool) = #externalMacro(
    module: "PowerAssertPlugin",
    type: "PowerAssertMacro"
)

12:07

// 宏展开后的代码
PowerAssert.Assertion("#assert(a == b)") {
    $0.capture(a, column: 8) == $0.capture(b, column: 13)
}

13:14

关键点:

  • 宏以 # 开头,如 #assert#Predicate
  • @freestanding(expression) 表示独立表达式宏
  • 宏参数会经过类型检查,和普通函数一样
  • 宏在编译期展开,生成普通 Swift 代码
  • Xcode 支持展开宏查看生成的代码,也支持在宏生成的代码中设断点

@Observable 宏简化数据观察

// 以前:ObservableObject + @Published
final class Person: ObservableObject {
    @Published var name: String
    @Published var age: Int
    @Published var isFavorite: Bool
}

struct ContentView: View {
    @ObservedObject var person: Person

    var body: some View {
        Text("Hello, \(person.name)")
    }
}

16:57

// Swift 5.9:@Observable 宏
@Observable final class Person {
    var name: String
    var age: Int
    var isFavorite: Bool
}

struct ContentView: View {
    var person: Person

    var body: some View {
        Text("Hello, \(person.name)")
    }
}

17:25

关键点:

  • @Observable@attached 宏,包含 member、memberAttribute 和 conformance 三个角色
  • 自动为每个属性添加观察追踪,无需手动写 @Published
  • 视图中不再需要 @ObservedObject,直接引用属性即可
  • 只更新实际读取的属性,粒度比 objectWillChange 更细

~Copyable 控制资源生命周期

// 问题:struct 可以随意复制,导致文件描述符重复关闭
struct FileDescriptor {
    private var fd: CInt

    init(descriptor: CInt) { self.fd = descriptor }

    func close() {
        Darwin.close(fd)
    }
}

// 修复:标记为不可复制
struct FileDescriptor: ~Copyable {
    private var fd: CInt

    init(descriptor: CInt) { self.fd = descriptor }

    func write(buffer: [UInt8]) throws { /* ... */ }

    consuming func close() {
        Darwin.close(fd)
    }

    deinit {
        Darwin.close(fd)
    }
}

26:06

// 编译器会在编译期阻止错误用法
let file = FileDescriptor(fd: descriptor)
file.close() // consuming 调用,file 的生命周期结束
file.write(buffer: data) // 编译错误:'file' used after consuming

27:20

关键点:

  • ~Copyable 表示值类型不可隐式复制
  • consuming func 表示调用此方法会消费掉值的所有权
  • 编译器追踪值的生命周期,在编译期阻止 use-after-consume
  • 适合管理文件描述符、网络连接、锁等不可复制的资源

C++ 互操作

// C++ 头文件 Person.h
// struct Person {
//     std::string name;
//     unsigned getAge() const;
// };
// std::vector<Person> everyone();

// Swift 中直接调用 C++ API
func greetAdults() {
    for person in everyone().filter { $0.getAge() >= 18 } {
        print("Hello, \(person.name)!")
    }
}

28:52

// Swift 代码
struct LabeledPoint {
    var x = 0.0, y = 0.0
    var label: String = "origin"
    mutating func moveBy(x deltaX: Double, y deltaY: Double) { /* ... */ }
    var magnitude: Double { /* ... */ }
}

// C++ 中调用 Swift
#include <Geometry-Swift.h>

void test() {
    Point origin = Point()
    Point unit = Point::init(1.0, 1.0, "unit")
    unit.moveBy(2, -2)
    std::cout << unit.label << " moved to " << unit.magnitude() << std::endl
}

29:51

关键点:

  • Swift 可以直接使用 C++ 的类、方法、属性和 STL 容器
  • C++ 可以通过生成的头文件调用 Swift 的结构体和方法
  • 支持值语义和引用语义的映射
  • 适合逐步将 C++ 代码库迁移到 Swift

自定义 Actor Executor

// 默认 Actor:使用系统管理的串行队列
actor MyConnection {
    private var database: UnsafeMutablePointer<sqlite3>

    init(filename: String) throws { /* ... */ }
    func pruneOldEntries() { /* ... */ }
}

// 自定义 Actor:绑定到指定的 DispatchQueue
actor MyConnection {
    private var database: UnsafeMutablePointer<sqlite3>
    private let queue: DispatchSerialQueue

    nonisolated var unownedExecutor: UnownedSerialExecutor {
        queue.asUnownedSerialExecutor()
    }

    init(filename: String, queue: DispatchSerialQueue) throws { /* ... */ }
    func pruneOldEntries() { /* ... */ }
}

35:30

关键点:

  • nonisolated var unownedExecutor 指定 Actor 使用的执行器
  • DispatchSerialQueue 内置支持 SerialExecutor 协议
  • 自定义执行器后,Actor 的所有方法都在指定队列上执行
  • 适合需要与特定线程/队列绑定的场景,如 SQLite、Core Data

核心启发

1. 用 if/switch 表达式替换项目中的嵌套三元运算符

搜索代码库中多层嵌套的 ?: 运算符,特别是用于变量初始化的场景。Swift 5.9 的 if 表达式让代码意图更清晰。入口是 Xcode 的查找功能,搜索 ?: 的嵌套模式。

2. 在新项目中直接用 @Observable 替代 ObservableObject

新项目的数据模型直接用 @Observable class Model,视图中不再需要 @ObservedObject。依赖追踪更精确,代码更简洁。入口是 import Observation,删除 @PublishedObservableObject 协议。

3. 探索参数包简化你的泛型 API

如果你有任何”为不同参数数量写重载”的 API,用参数包重构为一个函数。用户调用方式不变,维护成本大幅降低。入口是学习 eachrepeat 关键字语法。

4. 用宏消除项目中的样板代码

检查项目中重复出现的代码模式,如 Equatable 实现、Codable 的自定义键映射、枚举的 case 检测属性。这些都可以用宏自动化。入口是创建 Swift Package 类型的宏项目,参考 @CaseDetection 的实现。

5. 给底层资源管理类型加上 ~Copyable

如果你的项目中有管理文件描述符、网络句柄、GPU 资源的结构体,考虑标记为 ~Copyable 并添加 consuming 方法。编译器会在编译期捕获资源使用错误。入口是在资源包装类型上添加 : ~Copyable

关联 Session

评论

GitHub Issues · utterances