如何按多元素分隔符读取AsyncSequence至终止序列?
按多元素分隔符拆分AsyncSequence的方案
问题背景
我用两个Pipe和外部进程做读写交互,每个命令的响应都以"\n] "作为终止符。目前我通过readPipe.fileHandleForReading.bytes.characters这个AsyncSequence读取管道数据,想要读到这个终止序列就停止。我知道可以自己写个累积字符窗口、检查分隔符的自定义实现,但奇怪包括AsyncAlgorithms在内的工具里居然没有现成的方案,想问有没有类似的、能按多元素分隔符拆分/分块AsyncSequence的方法?
实际情况与解决方案
目前确实没有官方或主流工具库(比如AsyncAlgorithms)提供开箱即用的多元素分隔符拆分AsyncSequence的方法——主要是因为多字符分隔符的匹配需要维护滑动窗口缓冲区,逻辑比单字符分隔符复杂太多,属于偏定制化的场景,所以暂时没做成通用工具。
不过自己封装一个通用扩展也不算麻烦,核心思路就是攒元素到缓冲区,每次加新元素后就检查缓冲区末尾是否匹配分隔符,匹配上就输出前面的内容并清空对应部分。
给你写了个Swift的实现例子:
extension AsyncSequence where Element: Equatable { func split(by separator: [Element]) -> AsyncThrowingStream<[Element], Error> { AsyncThrowingStream { continuation in Task { var buffer: [Element] = [] let separatorLength = separator.count guard separatorLength > 0 else { continuation.finish() return } do { for try await element in self { buffer.append(element) // 缓冲区长度够分隔符长度才检查匹配 while buffer.count >= separatorLength { let checkStartIndex = buffer.count - separatorLength let candidateChunk = Array(buffer[checkStartIndex...]) if candidateChunk == separator { // 输出分隔符之前的内容 continuation.yield(Array(buffer[0..<checkStartIndex])) // 移除已匹配的部分(含分隔符) buffer.removeFirst(checkStartIndex + separatorLength) } else { break } } } // 序列结束后,把剩下的内容也输出 if !buffer.isEmpty { continuation.yield(buffer) } continuation.finish() } catch { continuation.finish(throwing: error) } } } } }
用的时候直接调用这个扩展就行:
let inputSequence = readPipe.fileHandleForReading.bytes.characters for try await chunk in inputSequence.split(by: ["\n", "]", " "]) { let responseString = String(chunk) // 处理拆分后的响应内容 print("获取到响应段:\(responseString)") }
这个实现支持任意Equatable类型的元素序列,不光是字符——比如你要是处理Data的话,把Element换成UInt8就能用。
另外,如果你懒得自己维护这个逻辑,可以去AsyncAlgorithms的仓库看看Issue,社区里不少人提过类似需求,说不定后续版本会加,但现在确实没现成的。
内容的提问来源于stack exchange,提问作者Rob Napier
相关产品推荐
相关产品推荐

