A Swift Tour: Explore Swift's features and design
SwiftUI & UI Frameworks 进阶 20m

Swift 导览:探索 Swift 的特性与设计

A Swift Tour: Explore Swift's features and design

2024年6月10日

在 Apple 官方观看视频

一句话判断

不管你是 Swift 新手还是老手,这场 Session 通过构建一个完整的社交网络后端,把 Swift 的核心设计哲学讲得通透——值语义、错误处理、协议与泛型,每个概念都有实际代码场景。

这场 Session 讲了什么

这是一场面向所有开发者的 Swift 语言导览,但不是简单罗列语法特性。演讲者 Allan Shortlidge 从 Swift 编译器团队的角度,围绕构建一个社交网络图数据模型,展示了 Swift 的核心设计原则如何在实际代码中发挥作用。

Session 的代码组织为一个 Swift Package,包含三个部分:图数据模型库、HTTP 服务器和一个命令行客户端。内容覆盖了值类型与引用类型的区别、错误处理模型(throw/try/catch、Optional、guard)、协议与泛型、闭包与高阶函数、并发模型(async/await、actor、Sendable),以及 Swift 的跨平台特性——从 Apple 平台到服务端,再到嵌入式设备(Embedded Swift)。

整场 Session 的叙事节奏很好:先建立基础概念(值语义、不可变性),再展示错误处理,然后引入类型抽象(协议+泛型),最后讨论并发。每一步都建立在前一步的基础上,适合系统性地理解 Swift 的设计思路。

值得深挖的点

值语义不是”struct vs class”那么简单

Session 开篇就用一个非常直观的例子解释了值语义:var x = 1; var y = x; x = 2 之后 y 仍然是 1。这看起来是理所当然的,但 Swift 把这个语义推广到了所有 struct 上——包括 Array、Dictionary 等集合类型,以及自定义的 struct。当你把 Alice 的 friends 数组赋值给 Bruno 的 friends 时,数组会被复制,所以修改 Alice 的 friends 不会影响 Bruno。

这个设计的意义在并发场景下变得特别明显。值类型实例之间不共享状态,所以不会出现两个线程同时修改同一个可变对象的情况。Swift 强调值类型不是偶然,而是为了在语言层面减少并发编程的心智负担。Session 还指出,Swift 中大多数你遇到的类型都是值类型,引用类型(class)的使用场景更加专门化——主要用于需要身份或共享可变状态的情况。

Swift 的错误处理三分法

Swift 把程序中的”出错”分成三种情况,对应不同的处理策略。第一种是可恢复错误:用 throw 标记可能失败的代码,用 try 调用,用 do/catch 处理。第二种是可选值:用 Optional 表示”可能有值也可能没有”的情况,强制调用方处理 nil。第三种是程序错误:比如数组越界访问,Swift 会直接终止程序,防止 bug 扩大为安全问题。

这个三分法的设计哲学是:可恢复错误应该包含足够上下文让调用方做决策(所以 enum 的 associated value 很重要),Optional 防止空指针崩溃(Swift 的 nil 安全是编译期保障),而程序错误(precondition failure)则不应该被 try-catch 捕获——它们意味着代码逻辑有 bug,应该修复代码而不是在运行时”处理”。

代码片段

场景一:用值类型和私有 setter 保护模型不变量

// 定义用户模型
struct User {
    var username: String
    var isVisible: Bool
    // 私有 setter:外部不能直接修改 friends 列表
    private(set) var friends: [String] = []

    // 通过方法控制修改,确保不变量
    mutating func addFriend(_ name: String) throws {
        // guard 语句:条件不满足时提前退出
        guard name != username else {
            throw UserError.selfFriend
        }
        guard !friends.contains(name) else {
            // associated value 提供错误上下文
            throw UserError.duplicateFriend(name)
        }
        friends.append(name)
    }
}

// 错误类型用 enum 定义,穷举所有可能
enum UserError: Error {
    case selfFriend
    case duplicateFriend(String)  // 携带上下文信息
}

// 使用时必须标记 try
do {
    var alice = User(username: "Alice", isVisible: true)
    try alice.addFriend("Bob")
    try alice.addFriend("Alice")  // 会抛出 selfFriend 错误
} catch {
    print("添加好友失败: \(error)")
}
// 坑点: mutating 方法只能用在 var 声明的实例上
// let 声明的 struct 实例不能调用 mutating 方法

场景二:协议 + 泛型实现可测试的架构

// 定义数据访问协议
protocol UserDatabase {
    func findUser(username: String) async -> User?
    func getAllUsers() async -> [User]
}

// 真实实现
struct LiveDatabase: UserDatabase {
    private var users: [String: User] = [:]

    func findUser(username: String) async -> User? {
        // Dictionary 下标返回 Optional,Swift 强制你处理 nil
        users[username]
    }

    func getAllUsers() async -> [User] {
        Array(users.values)
    }
}

// 测试用的 mock 实现
struct MockDatabase: UserDatabase {
    var mockUsers: [String: User]

    func findUser(username: String) async -> User? {
        mockUsers[username]
    }

    func getAllUsers() async -> [User] {
        Array(mockUsers.values)
    }
}

// 服务层依赖协议而非具体实现
struct UserService<DB: UserDatabase> {
    let database: DB

    func find(username: String) async -> User? {
        await database.findUser(username: username)
    }
}
// 坑点:泛型参数会让类型签名变长
// 但这个代价换来了编译期的类型安全和可测试性

场景三:async/await 与 actor 的并发模型

// 用 actor 保护共享可变状态
actor GraphServer {
    private var users: [String: User] = [:]

    func addUser(_ user: User) {
        users[user.username] = user
    }

    func findUser(username: String) -> User? {
        users[username]
    }

    // actor 的方法默认是隔离的,外部调用自动异步
    func getFriends(of username: String) -> [String] {
        users[username]?.friends ?? []
    }
}

// 使用 async/await 调用
let server = GraphServer()
await server.addUser(alice)

// 并发执行多个查询
async let friends1 = server.getFriends(of: "Alice")
async let friends2 = server.getFriends(of: "Bob")
// await 同时等待两个结果
let (result1, result2) = await (friends1, friends2)

// 坑点:async let 创建的子任务会立即开始执行
// 但错误需要在 await 时才会抛出
// 如果其中一个失败,另一个已经执行了,不会被取消
// 需要手动取消或使用 withTaskGroup

最佳实践

  • 迁移建议:如果你是从其他语言转来学 Swift 的,重点理解值语义和 Optional。值语义是 Swift 并发安全的基石,Optional 是 Swift 防止空指针崩溃的核心机制。这两个概念在别的语言中没有等价物。
  • 错误处理策略:用 throw 处理可恢复错误,用 Optional 表示”可能没有值”,用 precondition/fatalError 处理程序逻辑错误。不要把 Optional 当错误处理用——nil 不包含上下文信息,调用方无法知道”为什么没有值”。
  • 优先 struct:定义新类型时默认用 struct,只有当你需要引用语义(身份、共享可变状态、继承)时才用 class。这个原则会让你的代码天然更安全。

还有什么值得关注

  • Embedded Swift 项目让 Swift 可以运行在资源极其受限的嵌入式芯片上,打开了物联网场景
  • Swift 在服务端的应用已经覆盖了 Apple 的 iCloud Keychain、Photos、Notes 等核心服务,处理每秒百万级请求
  • Session 最后提到 Swift 的跨平台能力:同一个语言覆盖从嵌入式到服务端的完整光谱
WWDC 2024