大多数浏览器和
Developer App 均支持流媒体播放。
-
认识 AsyncSequence
随时间迭代值序列现在和编写“for”循环一样简单。了解新的 AsyncSequence 协议如何实现自然、简单的语法,将来自通知的任何内容迭代成服务器流式传输的字节。我们还将向您展示如何修改现有代码,以提供自己的异步序列。为了充分了解本节内容,我们建议先观看“认识 Swift 中的 async/await”。
资源
- SE-0298: Async/Await: Sequences
- SE-0314: AsyncStream and AsyncThrowingStream
- The Swift Programming Language: Concurrency
相关视频
WWDC22
WWDC21
-
下载
♪低音音乐播放♪ ♪ 菲利普豪斯勒:嗨 我叫菲利普 很高兴向你介绍 Swift中的一个很酷的新功能 AsyncSequence 今天我们将讨论什么是异步序列 以及它们背后的基本原理 然后我们将讨论 如何在代码中使用它们 并介绍一些新的AsyncSequence API 在最后 我们将探讨如何构建自己的异步序列 所以让我们直接开始吧 这是我编写的简单工具 用来说明 你能用AsyncSequence 做的一些很酷的新东西 在这个工具中 我们从一个 指向端点的URL开始 它列出了最近的地震 现在 通常下载东西实际上是一项 可能需要一些时间的异步任务 但是在这种情况下 我们不想等待所有的下载 相反地 我们想要展示收到的东西 所以我决定稍微改变一下 并使用新的async/await功能 来让代码行从端点获得响应 我们正在获取的数据 被格式化为逗号分隔的文本 所以每一行都是一个完整的数据行 由于代码行的AsyncSequence 在被收到时就会把每一行都发出去 这意味着我们可能有一个 非常大的下载量 但是通过在我们得到它们时发出它们 这些代码片段感觉非常合理 最棒的是你可以 在这个新的异步上下文常规序列中 使用你熟悉的相同东西 这意味着你可以使用新的 for-await-in语法 来迭代 以及map、filter 和reduce等功能 或者…就像在这个范例中一样… dropFirst函数 来操纵这些值 那么这是如何运作的呢? 好吧 我今天要讨论的很多事情 都是基于async/await座谈的 但让我们回顾一下几个关键点 通过使用await关键词 异步函数使你无需回调 即可编写并行代码 调用异步函数将挂起 然后在产生值或错误时恢复 另一方面 AsyncSequence 当底层迭代器产生一个值或抛出时 将在每个元素上挂起并恢复 基本上 顾名思义 它们就像常规的序列 但有一些关键的区别 也就是说 每个元素都是异步传递的 但是因为它们是异步交付 这意味着失败是绝对有可能的 一些异步序列抛出 但如果不允许失败 其他的就不会这么做 就像抛出的函数一样 在迭代或组合时 编译器将帮助 确保错误得到处理 一般来说 异步序列是一种描述 如何随着时间的推移生成值 所以AsyncSequence 可能是零个或多个值 然后通过从它的 迭代器返回一个nil来表示完成 就像序列一样 当发生错误时 这也是 AsyncSequence处于终止状态的点 并且在错误发生后 它们将在迭代器上 对next的任何后续调用返回nil 因此 让我们首先从常规迭代开始 深入了解该定义的工作原理 在这里 我们有一个非常熟悉的模式 这是一个for-in循环 在这种情况下 它会从一个序列中迭代地震 然后调用一个函数 当幅度超过某个值时 编译器知道这个迭代 应该如何工作 但它的作用并不稀奇 编译步骤 实际上只是做了一些简单的转换 让我们检查这些转换 以便你了解它的异步形式是什么 这大致上是编译器 在构建前面的代码时所做的 它首先通过创建一个迭代器变量开始 然后使用一个while循环来获取每次 调用next时 由迭代器生成的地震 要使用新的async/await功能 这里可以做一个小改动 就像将下一个函数 更改为异步函数一样简单 我们现在可以经由等待下一次地震 来让迭代参与Swift的并行性 让我们回过头来看看如果循环 在AsyncSequence上会是什么样子 如前所述 我们需要等待 AsyncSequence中的每一项 这反映在新的for-await-in语法中 这一切都意味着 如果你知道 如何使用Sequence 那你就对如何使用AsyncSequence 有了很好的了解 有几种方法可以利用异步序列 正如我们刚刚介绍的那样 你可以使用新的for-await-in语法 或者如果AsyncSequence抛出 你可以使用新的 for-try-await-in语法 这使你可以轻松迭代 异步产生的值 无需处理闭包 并使用你已经熟悉的语法 来迭代它们 甚至像break和continue 也能正常运作 现在你已经听完了 异步序列理论的介绍 让我们进一步探索该迭代 给定一个异步序列的源 你可以通过使用 for-await-in语法来等待每个值 这意味着它将等待 迭代器生成的每个项目 当它到达终端时 它完成了循环 在迭代异步序列时 break显然是从循环内部 提前终止迭代的好方法 这就像常规序列一样工作 当地震的位置 数据不存在时 我们就中断了 或者如果我们有一些想要跳过的值 我们可以使用continue 在这种情况下 如果深度大于某个值 我们将跳过那些 并继续等待下一次地震 下载的下一次迭代 会和以前一样运作 但在这种情况下 我们拥有的来源可能会引发错误 就像抛出函数一样 在被迭代的AsyncSequence 可以抛出时 每个元素都需要try来处理 并且就像抛出函数一样 编译器会检测你何时错过了try 并为你提供Fix-it来纠正错误 这意味着 在AsyncSequence可能产生错误时 你都会很安全 因为语言迫使你要嘛抛出那个错误 或抓住它 第二次迭代会在 第一个循环的迭代之后按顺序运行 顺序运行代码并不总是需要的 如果与其他正在发生的事情 并行运行迭代很有用 你可以创建一个 新的异步任务来封装迭代 当你知道正在使用的异步序列 可能无限期运行时 这会很有用 现在 即使该序列可能是不确定的 但发生的概率要低得多 但在异步行为的世界里 这是更常见的事情 以及使用它们时需要考虑的事项 值得庆幸的是Swift中的并行功能 让这件事变得非常简单和安全 这也很有帮助 当你想潜在从外部取消迭代时 这里我们可以同时运行两次迭代 并在稍后终止迭代 可以轻易地让任务确定 和某个容器的生命周期 一样无限期的工作范围 接下来让我们来看看 一些AsyncSequence API 可用于macOS Monterey iOS 15、tvOS 15和watchOS 8上 其中有很多 但我将向你展示一些亮点 从文件中读取通常是 异步行为的主要用例 FileHandle现在有一个新的字节属性 可以访问来自该FileHandle的 异步字节序列 这可以与AsyncSequence上的 将异步字节序列转换为代码行的 新扩展结合使用 但是处理文件是如此普遍 以至于我们决定URL 对于字节和代码行应该要有访问器 这与我在初始范例中 所使用的API相同 它是URL上的一个便利属性 将代码的AsyncSequence 从内容中返回 无论是来自文件还是网络 我确信这将使许多 以前非常复杂的任务变得容易和安全 有时从网络获取信息 需要对响应进行更多控制 和认证 所以URLSession现在有一个字节函数 获取给定URL或URLRequest的 AsyncSequence字节 如果你想了解更多 你一定要看看 《通过URLSession 使用async/await》座谈 来获取这方面的更多详细信息 以及URLSession的更多新异步功能 但是文件和网络并不是唯一 对AsyncSequence有意义的东西 现在可以使用新的通知API等待通知 迭代并不是与AsyncSequence 交互的唯一方式 在此范例中 我们正在等待 具有匹配存储UUID的 远程更改的第一个通知 使用firstWhere之类的方法 以及AsyncSequence通知 能允许一些简洁的新设计模式 做出的代码可以将先前的 复杂逻辑 转为紧凑和易读 如果所有这些还不够酷 还有很多新的API 来用于异步操作来自异步序列的值 这些应该很熟悉 因为它们是一些 在Sequence上可用的相同的功能 到目前为止 我们已经介绍了一些 例如dropFirst和firstWhere 但还有很多不仅是这些 几乎所有你能想到的 在Sequence上使用的东西 现在都有一个异步对应物 能与AsyncSequence配合使用 现在有这么多东西得搞懂 你可能会想 “嘿 那些新API真的很酷 而且语法非常简洁 但是我怎样才能 制作自己的异步序列呢?” 好吧 那就这么做吧! 实现AsyncSequence的方法有好几种 但我将专注于如何调整现有代码 特别是有一些设计模式 非常适合AsyncSequence 我们有一些很棒的功能 可以让你已经拥有的东西 与这个新概念进行交互 其中一些设计模式 就像多次调用的闭包 但也有一些代理也可以很好地工作 几乎任何不需要响应 而且只需要通知发生新值的东西 可以是制作AsyncSequence的 主要候选者 这些设计模式真的很常见 你现在的应用程序中 可能已经有了一些 这是常见处理程序模式的范例 它是一个具有处理程序属性 和启动以及停止方法的类 它似乎是AsyncSequence的 完美候选者 现有的用法可能是这样的 其中创建了一个监视器 并分配一个获取值的处理程序 然后启动监视器 以便可以将地震发送给处理程序 稍后 监视器可能会停止 以取消正在生成的事件 我们可以使用相同的接口来适应新的 AsyncStream类型的用法 只需少量代码即可使用它 并允许你构建AsyncSequence 在构建AsyncStream时 元素类型和构造闭包是有明确指定的 闭包有一个延续 可以多次产生值、完成 或处理终止 所以这意味着 在这种情况下 可以在构造闭包内创建监视器 然后可以分配处理程序 来延续地震的产生 然后onTermination可以处理取消 和清理 然后我们就可以开始监控了 我们之前拥有的 这个相同的监视器代码 可以在AsyncStream的 构造中轻松封装 这减少了在每个使用站点中 复制相同逻辑的需要 这就是AsyncStream的使用方式 你可以使用强大的转换功能 例如说过滤器 以及新的for-await-in语法 这让你可以专注于代码的意图 而无需担心 复制簿记 因为一切都集中在一个地方 AsyncStream还有很大的灵活性 来创建你自己的异步序列 这实际上只是一个例子 还有更多其他例子 可以让你 在你的代码中采用 AsyncStream是将现有代码调整为 AsyncSequence的好方法 它可以处理所有你期望 AsyncSequence的事情 例如安全、迭代 和取消 但他们也处理缓冲 AsyncStream是一种可靠的方式 来让你构建自己的异步序列 以及来自你自己的API的 合适的返回类型 因为唯一元素的生产来源 就是来自建设 如果你需要表示抛出的错误呢? 这个嘛 我们给它准备了一个类型! AsyncThrowingStream 就像AsyncStream 但可以处理错误 它提供与AsyncStream 相同的灵活性和安全性 但是可以通过从迭代中抛出处理失败 AsyncSequence 是一个非常强大的工具 既安全又熟悉 用于处理多个异步值 如果你知道如何使用Sequence 你已经知道 如何使用AsyncSequence了 我们已经讨论了什么是异步序列 以及它们是如何使用的 并向你介绍了AsyncStream 我们已经深入研究了理论 和它们的定义 以及一些新引入的异步序列 以及最后 如何自己构建它们 我热切地期待你们接下来 能用它做出什么来 ♪
-
-
0:37 - QuakesTool
@main struct QuakesTool { static func main() async throws { let endpointURL = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_month.csv")! // skip the header line and iterate each one // to extract the magnitude, time, latitude and longitude for try await event in endpointURL.lines.dropFirst() { let values = event.split(separator: ",") let time = values[0] let latitude = values[1] let longitude = values[2] let magnitude = values[4] print("Magnitude \(magnitude) on \(time) at \(latitude) \(longitude)") } } }
-
3:24 - Iterating a Sequence
for quake in quakes { if quake.magnitude > 3 { displaySignificantEarthquake(quake) } }
-
3:52 - How the compiler handles iteration
var iterator = quakes.makeIterator() while let quake = iterator.next() { if quake.magnitude > 3 { displaySignificantEarthquake(quake) } }
-
4:11 - How the compiler handles asynchronous iteration
var iterator = quakes.makeAsyncIterator() while let quake = await iterator.next() { if quake.magnitude > 3 { displaySignificantEarthquake(quake) } }
-
4:28 - Iterating an AsyncSequence
for await quake in quakes { if quake.magnitude > 3 { displaySignificantEarthquake(quake) } }
-
5:36 - Terminating iteration early by breaking
for await quake in quakes { if quake.location == nil { break } if quake.magnitude > 3 { displaySignificantEarthquake(quake) } }
-
5:51 - Skipping values by continuing
for await quake in quakes { if quake.depth > 5 { continue } if quake.magnitude > 3 { displaySignificantEarthquake(quake) } }
-
6:05 - AsyncSequence might throw
do { for try await quake in quakeDownload { ... } } catch { ... }
-
7:15 - Concurrently iterating inside an async task
let iteration1 = Task { for await quake in quakes { ... } } let iteration2 = Task { do { for try await quake in quakeDownload { ... } } catch { ... } } //... later on iteration1.cancel() iteration2.cancel()
-
7:56 - Reading bytes from a FileHandle
for try await line in FileHandle.standardInput.bytes.lines { ... }
-
8:16 - Reading lines from a URL
let url = URL(fileURLWithPath: "/tmp/somefile.txt") for try await line in url.lines { ... }
-
8:49 - Reading bytes from a URLSession
let (bytes, response) = try await URLSession.shared.bytes(from: url) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 /* OK */ else { throw MyNetworkingError.invalidServerResponse } for try await byte in bytes { ... }
-
9:12 - Notifications
let center = NotificationCenter.default let notification = await center.notifications(named: .NSPersistentStoreRemoteChange).first { $0.userInfo[NSStoreUUIDKey] == storeUUID }
-
11:10 - Using an AsyncStream
class QuakeMonitor { var quakeHandler: (Quake) -> Void func startMonitoring() func stopMonitoring() } let quakes = AsyncStream(Quake.self) { continuation in let monitor = QuakeMonitor() monitor.quakeHandler = { quake in continuation.yield(quake) } continuation.onTermination = { @Sendable _ in monitor.stopMonitoring() } monitor.startMonitoring() } let significantQuakes = quakes.filter { quake in quake.magnitude > 3 } for await quake in significantQuakes { ... }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。