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,删除 @Published 和 ObservableObject 协议。
3. 探索参数包简化你的泛型 API
如果你有任何”为不同参数数量写重载”的 API,用参数包重构为一个函数。用户调用方式不变,维护成本大幅降低。入口是学习 each 和 repeat 关键字语法。
4. 用宏消除项目中的样板代码
检查项目中重复出现的代码模式,如 Equatable 实现、Codable 的自定义键映射、枚举的 case 检测属性。这些都可以用宏自动化。入口是创建 Swift Package 类型的宏项目,参考 @CaseDetection 的实现。
5. 给底层资源管理类型加上 ~Copyable
如果你的项目中有管理文件描述符、网络句柄、GPU 资源的结构体,考虑标记为 ~Copyable 并添加 consuming 方法。编译器会在编译期捕获资源使用错误。入口是在资源包装类型上添加 : ~Copyable。
关联 Session
- Write Swift macros — 从零编写 Swift 宏的完整教程
- Expand on Swift macros — 宏的高级用法和调试技巧
- Beyond the basics of structured concurrency — Actor 和并发进阶
- Use Swift with C++ — Swift 与 C++ 互操作的详细指南
评论
GitHub Issues · utterances