作为 Swift 向安全、简单和高性能异步编程迈进的一部分,Swift 核心团队引入了一个新的算法包:AsyncSequence。这个算法包被称为 Swift Async Algorithms,现在已开源并可以在 GitHub 上获取 https://github.com/apple/swift-async-algorithms。

这个包有三个主要目标:

  • 与 async/await 的集成
  • 为基于时间的算法提供一个归宿
  • 跨平台和开源

目标

AsyncAlgorithms 是一个算法包,用于处理随时间变化的值。这包括主要与时间有关的算法,例如 debounce 和 throttle,还包括有关顺序的算法,例如 combineLatest 和 merge。使用多个输入的操作(如 zip 对 Sequence 所做的操作)实现起来可能非常复杂,需要考虑微妙的行为和许多边缘情况。共享包可以通过大量测试和文档来正确处理这些细节,以造福所有 Swift 应用程序。

AsyncAlgorithms 的基础能力已经包含在 Swift 5.5 的 AsyncSequence 中。Swift 5.5 还带来了使用自然 for/in 循环和 await 来处理 AsyncSequence 和序列等效 API(如 map 和 filter)中的值的能力。结构化并发允许我们编写中间状态只是一个局部变量的代码,try 可以直接用于抛出异常的函数,并且异步代码的逻辑与同步代码类似。

我们相信开源包将为这些 API 提供一个很好的归宿。软件包使开发人员可以灵活地跨平台和操作系统版本进行部署。开发和 API 设计将在 GitHub 和 Swift 论坛上进行。

简要概览

该软件包包括常用算法的 AsyncSequence 版本,例如:

  • Zip
  • CombineLatest
  • Merge
  • Chain
  • Buffer
  • Debounce
  • Throttle

让我们从 zip 开始。与其对应的序列一样,zip 生成由来自两个不同 AsyncSequence 的值组成的元组:

for await (number, letter) in zip(numbers, letters) {
    print(number, letter)
}

AsyncSequence 支持 rethrows,这意味着错误处理就像使用 try 一样简单——与任何其他 Swift 代码一样:

do {
    for try await (number, letter) in zip(numbers, lettersWithErrors) {
        print(number, letter)
    }
} catch {
    // Handle error
}

其他算法,如 combineLatest 和 merge 也组合了几个 AsyncSequence 的输出。每个都对输出的类型和时间提供不同类型的控制。

Sequence 和 AsyncSequence 的一个根本区别是引入了时间变量。基于 proposals to standardize Clock and Duration 这个 proposal https://github.com/apple/swift-evolution/blob/main/proposals/0329-clock-instant-duration.md ,该软件包添加了 debounce 和 throttle 等算法。它们为常见操作提供简单、开箱即用的解决方案,例如丢弃过快到达的值:

for await value in input.debounce(for: .seconds(0.5)) {
    // Handle input, at most once per 0.5 seconds.
}

在有限的异步序列中等待所有值的收集通常很有用。这个包提供了初始化器,只需一行代码即可:

let result = await Array(input)

async 函数对于将同步序列与 AsyncSequence 结合起来很有用。在这里,我们将它与 chain 函数一起使用来为文件的内容添加前导:

let preamble = [
    "// This source file is part of the Swift.org open source project"
    "//"
    ""
].async

let lines = chain(preamble, URL(fileURLWithPath: "/tmp/Sample.swift").lines)

for try await line in lines {
    print(line)
}

Combine

Apple 在 iOS 13 和 macOS 10.15 SDK 中引入了 Combine 框架。从那时起,我们就有机会了解如何在现实场景中使用 Combine。通过 AsyncAlgorithms,我们可以应用这些能力,并采用 Swift 的新结构化并发特性。

Combine API 基于 Publisher 和 Subscriber 接口,通过操作符在它们之间进行连接。它的设计重点是提供一种以声明方式指定这些运算符链的方法,在数据从一端移动到另一端时对其进行转换。这需要对中间状态进行不同的思考。有时这会导致调用站点比人们预期的更复杂——尤其是在处理需要共享的单个值、错误或数据时。async/await 的结构化并发为我们提供了一种新的方式来表达这种逻辑。我们现在可以编写异步代码,将其分成更小的部分并从上到下读取,而不是作为一系列链式转换。

我们对 async/await 和 AsyncSequence 为语言带来的可能性感到兴奋。我们相信这个包将是探索该领域更高级别 API 的未来发展和演变的好地方。

下一步是什么

Swift 官方现在发布了 Swift 异步算法包的原型版本。目的是通过有效的实现来启动项目,然后在 Swift 论坛上进行详细的设计讨论。欢迎社区参与:

  • 早期采用该软件包和对设计的反馈
  • 包的实现
  • 测试的实施
  • 包未来的宣传和演变

官方也正在使用 GitHub 问题来跟踪错误、功能请求和启动任务。