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

What's new in Swift

观看原视频

Highlight

Swift 5.7 带来了包管理器插件、分布式 actor、原生 Regex、简化的泛型语法、C 语言互操作改进等大量新特性,同时 docC 和 Swift.org 网站正式开源。

核心内容

包管理器插件:让构建流程自动化

你的项目里总有一些重复性工作:生成代码、运行文档工具、格式化源码。以前这些要么靠脚本,要么靠 Xcode 的 Build Phase,维护起来很分散。Swift 5.7 给 SPM 加了两种插件:Command Plugin 像 Makefile 目标,可以从命令行调用;Build Tool Plugin 更强大,能在编译前/后自动执行。同时还引入了 module aliases 解决同名模块冲突问题。

Swift 并发:走向 Swift 6

Swift 5.7 在并发安全方面迈出了重要一步。编译器现在能检测更多数据竞争场景,包括闭包内捕获可变状态和跨任务的数据竞争。分布式 actor(distributed actor)是最重要的并发新特性,它把 actor 模型从单进程扩展到多进程,调用远程 actor 和调用本地 actor 语法完全一致。

详细内容

包管理器插件:让构建流程自动化

07:19)Swift 5.7 给 SPM 加了两种插件。Command Plugin 像 Makefile 目标,你可以从命令行调用:

import PackagePlugin

@main struct MyPlugin: CommandPlugin {
    func performCommand(context: PluginContext, arguments: [String]) throws {
        let doccExec = try context.tool(named: "docc").path
        let doccArgs = ["convert", "--output-path", "docs"]
        let process = try Process.run(doccExec, arguments: doccArgs)
        process.waitUntilExit()
    }
}

关键点:

  • CommandPlugin 从命令行触发,适合文档生成、代码格式化等任务
  • context.tool(named:) 获取工具路径,不用硬编码

Build Tool Plugin 更强大,它能在编译前/后自动执行:

import PackagePlugin

@main struct MyCoolPlugin: BuildToolPlugin {
    func createBuildCommands(context: TargetBuildContext) throws -> [Command] {
        let generatedSources = context.pluginWorkDirectory.appending("GeneratedSources")

        return [
            .buildCommand(
                displayName: "Running MyTool",
                executable: try context.tool(named: "mycooltool").path,
                arguments: ["create"],
                outputFilesDirectory: generatedSources
            )
        ]
    }
}

关键点:

  • BuildToolPlugin 在编译流程中自动执行
  • outputFilesDirectory 声明生成的文件,Xcode 会自动把它们加入编译

09:23)还有一个长期痛点:两个依赖包导出了同名模块。Swift 5.7 的 module aliases 解决了这个问题:

let package = Package(
    name: "MyStunningApp",
    dependencies: [
        .package(url: "https://.../swift-metrics.git"),
        .package(url: "https://.../swift-log.git")
    ],
    targets: [
        .executableTarget(
            name: "MyStunningApp",
            dependencies: [
                .product(name: "Logging", package: "swift-log"),
                .product(name: "Metrics", package: "swift-metrics",
                         moduleAliases: ["Logging": "MetricsLogging"]),
            ])
    ]
)

使用时两个模块名不再冲突:

import Logging           // from swift-log
import MetricsLogging    // from swift-metrics

let swiftLogger = Logging.Logger()
let metricsLogger = MetricsLogging.Logger()

Swift 并发:走向 Swift 6

13:14)Swift 5.7 在并发安全方面迈出了重要一步。编译器现在能检测更多数据竞争场景:

var numbers = [3, 2, 1]

numbers.removeAll(where: { number in
    number == numbers.count  // 编译器警告:在闭包中捕获了可变状态
})

14:10)跨任务的数据竞争也会被捕获:

var numbers = [3, 2, 1]

Task { numbers.append(0) }  // 在另一个任务中修改
numbers.removeLast()         // 编译器警告:数据竞争

15:54)分布式 actor(distributed actor)是 Swift 5.7 最重要的并发新特性之一。它把 actor 模型从单进程扩展到多进程:

import Distributed

distributed actor Player {
    var ai: PlayerBotAI?
    var gameState: GameState

    distributed func makeMove() -> GameMove {
        return ai.decideNextMove(given: &gameState)
    }
}

关键点:

  • distributed actor 声明一个可能位于远程的 actor
  • distributed func 标记可以跨网络调用的方法
  • 参数和返回值需要满足序列化要求(默认 Codable)

调用远程 actor 和调用本地 actor 语法完全一致:

func endOfRound(players: [Player]) async throws {
    for player in players {
        let move = try await player.makeMove()
    }
}

语言简化:更干净的语法

20:12)Swift 5.7 简化了 optional unwrapping。以前要写:

if let mailmapURL = mailmapURL {
    mailmapLines = try String(contentsOf: mailmapURL).split(separator: "\n")
}

变量名短了可读性差,长了又冗余。Swift 5.7 允许省略右侧:

if let workingDirectoryMailmapURL {
    mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n")
}

guard let workingDirectoryMailmapURL else { return }
mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n")

21:07)闭包类型推断也改进了。以前复杂闭包需要显式标注类型,现在编译器能从上下文推断:

let entries = mailmapLines.compactMap { line in
    try? parseLine(line)
}

func parseLine(_ line: Substring) throws -> MailmapEntry { }

22:15)C 语言互操作方面,Swift 5.7 改进了指针类型转换:

func removeDuplicates(from map: UnsafeMutablePointer<mailmap_t>) {
    var size = mailmap_get_size(map)
    size -= moveDuplicatesToEnd(map)
    withUnsafeMutablePointer(to: &size) { signedSizePtr in
        signedSizePtr.withMemoryRebound(to: UInt32.self, capacity: 1) { unsignedSizePtr in
            mailmap_truncate(map, unsignedSizePtr)
        }
    }
}

关键点:

  • withMemoryRebound 安全地在 IntUInt32 之间转换指针
  • 不再需要手动管理内存布局

Swift Regex:字符串处理的新方式

23:41)字符串解析一直是 Swift 的痛点。以前用 String 的索引操作写起来冗长且容易出错:

func parseLine(_ line: Substring) throws -> MailmapEntry {
    func trim(_ str: Substring) -> Substring {
        String(str).trimmingCharacters(in: .whitespacesAndNewlines)[...]
    }

    let activeLine = trim(line[..<(line.firstIndex(of: "#") ?? line.endIndex)])
    guard let nameEnd = activeLine.firstIndex(of: "<"),
          let emailEnd = activeLine[nameEnd...].firstIndex(of: ">"),
          trim(activeLine[activeLine.index(after: emailEnd)...]).isEmpty else {
        throw MailmapError.badLine
    }

    let name = nameEnd == activeLine.startIndex ? nil : trim(activeLine[..<nameEnd])
    let email = activeLine[activeLine.index(after: nameEnd)..<emailEnd]

    return MailmapEntry(name: name, email: email)
}

25:10)Swift 5.7 引入了原生 Regex,支持字面量和强类型捕获:

func parseLine(_ line: Substring) throws -> MailmapEntry {
    let regex = /\h*([^<#]+?)??\h*<([^>#]+)>\h*(?:#|\Z)/

    guard let match = line.prefixMatch(of: regex) else {
        throw MailmapError.badLine
    }

    return MailmapEntry(name: match.1, email: match.2)
}

关键点:

  • /.../ 是 Regex 字面量,编译器会检查语法
  • match.1match.2 是强类型捕获,类型是 Substring

26:34)对于更复杂的场景,Regex Builder 提供了声明式语法:

import RegexBuilder

let regex = Regex {
    ZeroOrMore(.horizontalWhitespace)

    Optionally {
        Capture(OneOrMore(.noneOf("<#")))
    }
    .repetitionBehavior(.reluctant)

    ZeroOrMore(.horizontalWhitespace)

    "<"
    Capture(OneOrMore(.noneOf(">#")))
    ">"

    ZeroOrMore(.horizontalWhitespace)
    ChoiceOf {
        "#"
        Anchor.endOfSubjectBeforeNewline
    }
}

27:05)Regex 组件可以复用,甚至可以嵌入 Foundation 的解析器:

struct DatedMailmapLine: RegexComponent {
    @RegexComponentBuilder
    var regex: Regex<(Substring, Substring?, Substring, Date)> {
        ZeroOrMore(.horizontalWhitespace)

        Optionally {
            Capture(OneOrMore(.noneOf("<#")))
        }
        .repetitionBehavior(.reluctant)

        ZeroOrMore(.horizontalWhitespace)

        "<"
        Capture(OneOrMore(.noneOf(">#")))
        ">"

        ZeroOrMore(.horizontalWhitespace)

        Capture(.iso8601.year().month().day())

        ZeroOrMore(.horizontalWhitespace)
        /#|\Z/
    }
}

关键点:

  • RegexComponent 协议让你把常用模式封装成可复用组件
  • .iso8601.year().month().day() 直接嵌入 Foundation 的日期解析器
  • 捕获的类型是 Date,不是字符串

泛型语法简化

29:02)Swift 5.7 让泛型代码写起来更自然。以前协议作为类型使用时限制很多:

// 以前:只能用 generic 函数
func addEntries1<Map: Mailmap>(_ entries: Array<MailmapEntry>, to mailmap: inout Map)

// 以前:protocol 不能直接作为类型
func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout Mailmap)  // 编译错误

31:05)Swift 5.7 引入了显式的 any 语法:

func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout any Mailmap) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}

关键点:

  • any Mailmap 明确表示这是一个 existential type
  • 带关联类型的协议也可以作为 any 类型使用

32:54)主关联类型(primary associated type)让约束表达更简洁:

// 以前
func addEntries1<Entries: Collection, Map: Mailmap>(_ entries: Entries, to mailmap: inout Map)
    where Entries.Element == MailmapEntry

// Swift 5.7
func addEntries1(_ entries: some Collection<MailmapEntry>, to mailmap: inout some Mailmap)

// 或者用 any
func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap)

关键点:

  • some Collection<MailmapEntry> 等价于 some Collection where Element == MailmapEntry
  • any 用于需要异构存储的场景,some 用于隐藏具体类型

核心启发

1. 用 SPM 插件自动化项目工作流

把代码生成、文档构建、格式化检查等任务写成 SPM 插件。Command Plugin 适合 CI/CD 和本地开发命令,Build Tool Plugin 适合代码生成。检查你的项目里有哪些 shell 脚本可以迁移。

入口:PackagePlugin 框架,CommandPlugin / BuildToolPlugin 协议

2. 用 Regex Builder 替换字符串解析代码

项目里所有用 String.index 手工解析的地方,都值得看看能不能用 Regex 或 Regex Builder 替代。特别是日志解析、配置文件读取、数据格式转换这类场景。

入口:RegexBuilder 模块,RegexComponent 协议

3. 为 Swift 6 的严格并发检查做准备

开启编译器的 Strict Concurrency 检查,逐步给类型标注 Sendable。分布式 actor 为跨进程通信提供了类型安全的抽象,适合游戏、协作工具等需要实时通信的 App。

入口:-strict-concurrency=completeSendable 协议,distributed actor

4. 简化现有泛型代码

检查项目中手写的类型擦除包装器(AnyPublisherAnyCollection 等自定义版本),看看能不能直接换成 any 语法。新的 some + 主关联类型写法让 API 签名更短更清晰。

入口:any ProtocolName<AssociatedType>some ProtocolName<AssociatedType>

关联 Session

评论

GitHub Issues · utterances