Highlight
AsyncSequence是 Swift 5.5 引入的核心协议,它是同步Sequence的异步等价物。就像for item in sequence遍历同步序列一样,for await item in asyncSequence可以遍历异步产生的元素。
核心内容
在 async/await 之前,处理一系列异步值需要回调、代理模式或 Combine Publisher。这些方式各有各的复杂性:回调嵌套导致”回调地狱”,代理分散在多个方法中,Combine 的学习曲线又比较陡。
AsyncSequence 用最朴素的 for-await-in 循环解决了这个问题。它让异步序列的处理看起来和同步序列几乎一样。编译器在背后会把 for-await-in 转换成对 makeAsyncIterator() 的调用,然后反复 await iterator.next()。
系统框架中大量 API 已经提供了 AsyncSequence 接口。URL.lines 可以逐行异步读取文本文件。URLSession 的 bytes(from:) 返回 AsyncBytes。NotificationCenter 的 notifications(named:) 返回通知序列。FileHandle 也支持了异步读取。
Session 还介绍了 AsyncStream 和 AsyncThrowingStream,这是把传统的 callback/delegate 模式转换为 AsyncSequence 的桥梁。你可以用 AsyncStream 把 Timer、CLLocationManager 的 delegate 回调包装成 AsyncSequence。
详细内容
遍历 AsyncSequence
(04:28)
for await quake in quakes {
if quake.magnitude > 3 {
displaySignificantEarthquake(quake)
}
}
关键点:
for await是 Swift 5.5 的新语法,用于遍历AsyncSequence- 如果序列可能抛出错误,使用
for try await break和continue在for-await-in中正常工作
for try await quake in quakeDownload {
if quake.location == nil {
break
}
if quake.magnitude > 3 {
displaySignificantEarthquake(quake)
}
}
编译器如何处理异步迭代
(03:52)
// 同步迭代
var iterator = quakes.makeIterator()
while let quake = iterator.next() {
if quake.magnitude > 3 {
displaySignificantEarthquake(quake)
}
}
// 异步迭代
var iterator = quakes.makeAsyncIterator()
while let quake = await iterator.next() {
if quake.magnitude > 3 {
displaySignificantEarthquake(quake)
}
}
关键点:
- 编译器把
for-in转换成while let+makeIterator()+next() - 编译器把
for await-in转换成while let await+makeAsyncIterator()+next() - 每次迭代都可能挂起,等待下一个值到达
并发迭代
(07:15)
let iteration1 = Task {
for await quake in quakes {
displayEarthquake(quake)
}
}
let iteration2 = Task {
do {
for try await quake in quakeDownload {
updateMap(with: quake)
}
} catch {
handleError(error)
}
}
// 后续可以取消
iteration1.cancel()
iteration2.cancel()
关键点:
- 把
for-await-in放在Task闭包中可以并发执行多个迭代 Task提供了cancel()方法,可以从外部取消迭代- 对于可能无限运行的序列(如通知流),这种方式可以管理生命周期
从 FileHandle 读取字节
(07:56)
for try await line in FileHandle.standardInput.bytes.lines {
processLine(line)
}
关键点:
FileHandle.bytes返回AsyncSequence<UInt8>.lines扩展把字节流转换成字符串流,按行分割- 适合处理标准输入、管道数据等流式场景
从 URL 读取文件
(08:16)
let url = URL(fileURLWithPath: "/tmp/somefile.txt")
for try await line in url.lines {
print(line)
}
关键点:
URL.lines是 Swift 5.5 新增的便利属性- 自动处理文件打开、关闭和错误
- 逐行读取,不会一次性把整个文件加载到内存
从 URLSession 获取字节流
(08:49)
let (bytes, response) = try await URLSession.shared.bytes(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw MyNetworkingError.invalidServerResponse
}
for try await byte in bytes {
processByte(byte)
}
关键点:
bytes(from:)返回(AsyncBytes, URLResponse)元组AsyncBytes是AsyncSequence<UInt8>- 逐字节处理,适合流式解析或大文件下载
等待特定通知
(09:12)
let center = NotificationCenter.default
let notification = await center.notifications(named: .NSPersistentStoreRemoteChange).first {
$0.userInfo[NSStoreUUIDKey] == storeUUID
}
关键点:
notifications(named:)返回AsyncSequence<Notification>- 结合
first(where:)可以等待第一个满足条件的通知 - 这比实现 delegate 回调或添加 observer 更简洁
使用 AsyncStream
(11:10)
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 {
displaySignificantEarthquake(quake)
}
关键点:
AsyncStream构造函数接受一个闭包,提供continuation用于产生值continuation.yield()向序列投递一个值continuation.onTermination在序列被取消时调用,用于清理资源AsyncStream可以和filter、map等操作符组合使用
AsyncStream 处理错误
如果需要抛出错误,使用 AsyncThrowingStream:
let events = AsyncThrowingStream(Event.self) { continuation in
let monitor = EventMonitor()
monitor.eventHandler = { result in
switch result {
case .success(let event):
continuation.yield(event)
case .failure(let error):
continuation.finish(throwing: error)
}
}
monitor.start()
}
关键点:
AsyncThrowingStream的continuation.finish(throwing:)用于报告错误- 迭代时需要使用
for try await - 错误会终止序列,后续
next()调用返回nil
核心启发
1. 用 URL.lines 处理大文件
如果你的 App 需要处理大型文本文件(日志、CSV、JSON Lines),不要一次性读入内存。用 url.lines 逐行处理,内存占用恒定。
实现思路:替换 String(contentsOfFile:) 为 for try await line in url.lines。在循环内逐行解析,提取所需信息后丢弃。
2. 用 AsyncStream 包装 Timer 回调
传统的 Timer.scheduledTimer 需要在 target 方法中处理,分散了逻辑。用 AsyncStream 包装后,可以在一个循环中处理所有定时事件。
实现思路:创建 AsyncStream,在 Timer 回调中调用 continuation.yield(())。然后用 for await _ in timerStream 处理定时任务。
3. 用 AsyncStream 包装 NotificationCenter 回调
某些通知需要特定条件触发,传统方式需要先 add observer 再在回调中判断条件。用 notifications(named:) 的 first(where:) 可以直接等待满足条件的通知。
实现思路:替换 addObserver(forName:object:queue:using:) 为 await center.notifications(named:).first { 条件 }。代码更简洁,意图更明确。
4. 用 AsyncStream 包装网络分页加载
网络 API 的分页加载通常需要递归调用下一页。用 AsyncStream 可以把分页序列变成线性迭代。
实现思路:创建 AsyncStream,在异步闭包中加载第一页,通过 continuation.yield 返回每条数据,然后递归加载下一页。调用方只需 for await item in paginatedStream。
5. 用 AsyncStream 包装 CoreLocation 回调
CLLocationManager 的 delegate 回调分散在多个方法中。用 AsyncStream 可以统一处理位置更新。
实现思路:为每个需要的事件创建 AsyncStream(如 didUpdateLocations、didFailWithError),在 delegate 方法中调用对应的 continuation.yield。
关联 Session
- Meet async/await in Swift — Swift 5.5 引入的 async/await 语法详解
- Explore structured concurrency in Swift — Swift 结构化并发模型
- Discover concurrency in SwiftUI — 在 SwiftUI 中使用并发特性
- Protect mutable state with Swift actors — 使用 Actor 避免数据竞争
评论
GitHub Issues · utterances