前言
日常开发里,我们写 for item in list 像呼吸一样自然。
但 Swift 编译器在背后悄悄做了三件事:
- 调用
list.makeIterator()拿到一个迭代器 - 反复调用
iterator.next() - 把返回的可选值解包后赋给
item
一旦理解这三步,你就能
- 自己写"能 for-in 的数据结构"
- 避免"遍历同时修改"导致的崩溃
- 把回调式 API 优雅地转成
AsyncSequence
Sequence:最小迭代单元
协议定义(核心部分,Swift 5.9 仍不变)
swift
public protocol Sequence {
associatedtype Element
associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
func makeIterator() -> Iterator
}
public protocol IteratorProtocol {
associatedtype Element
mutating func next() -> Element?
}
关键知识点
- Sequence 只承诺"能生成迭代器",不保证能反复遍历,也不保证有
count。 - 迭代器几乎总是
struct:值语义保证"复制一份就从头开始",不会意外共享状态。 - 单趟序列(例如网络流)完全合法;第二次
makeIterator()可以返回空迭代器。
代码示例:自定义一个"从 n 倒数到 0"的序列
swift
struct Countdown: Sequence {
let start: Int
// 每次 for-in 都会调用一次,生成新的迭代器
func makeIterator() -> Iterator {
Iterator(current: start)
}
struct Iterator: IteratorProtocol {
var current: Int
// 返回 nil 时代表迭代结束
mutating func next() -> Int? {
guard current >= 0 else { return nil }
defer { current -= 1 } // 先返回,再减
return current
}
}
}
// 使用
for number in Countdown(start: 3) {
print(number) // 3 2 1 0
}
Collection:在 Sequence 上加了三把锁
Collection 额外保证
- 可多次遍历且顺序稳定(除非自己把文档写错)
- 提供
count、endIndex、下标访问 - 支持切片、前缀、后缀等默认实现
协议片段
swift
public protocol Collection: Sequence {
associatedtype Index: Comparable
var startIndex: Index { get }
var endIndex: Index { get }
subscript(position: Index) -> Element { get }
func index(after i: Index) -> Index
}
因为多趟安全,map、filter 可以提前分配内存;
因为下标存在,Array、Dictionary、Set 都直接 conform。
for-in 的糖衣剥开长这样
编译器把
swift
for element in container {
print(element)
}
翻译成
swift
var iterator = container.makeIterator()
while let element = iterator.next() {
print(element)
}
理解这段模板代码,你就能:
- 在 Playground 里手动模拟
for循环 - 把"遍历同时修改"的崩溃场景复现出来
遍历同时修改:崩溃现场与三种安全写法
现场:遍历数组时删除元素
swift
var todoItems = ["A", "B", "C"]
// 目前倒是没有崩溃,但是也不是很符合逻辑
for (index, item) in todoItems.enumerated() {
if item == "B" {
todoItems.remove(at: index) // ❌ Fatal error: Collection modified while enumerating
}
}
原因:数组缓冲区搬迁,迭代器指针失效。
三种安全写法:
- 官方一次性 API
swift
todoItems.removeAll { $0 == "B" }
- 先记下索引,后删除
swift
let indexesToRemove = todoItems.indices.filter { todoItems[$0] == "B" }
for i in indexesToRemove.reversed() {
todoItems.remove(at: i)
}
- 过滤后整体替换
swift
todoItems = todoItems.filter { $0 != "B" }
AsyncSequence:把"迭代"搬到异步世界
协议定义
swift
public protocol AsyncSequence {
associatedtype Element
associatedtype AsyncIterator: AsyncIteratorProtocol where AsyncIterator.Element == Element
func makeAsyncIterator() -> AsyncIterator
}
public protocol AsyncIteratorProtocol {
associatedtype Element
mutating func next() async throws -> Element?
}
消费方式
swift
for await element in stream {
print(element) // 会在每次 next() 挂起时让出线程
}
桥接回调式 API 的模板:进度条场景
swift
func makeProgressStream() -> AsyncStream<Double> {
AsyncStream { continuation in
let token = ProgressCenter.onUpdate { value in
continuation.yield(value)
if value >= 1.0 { continuation.finish() }
}
continuation.onTermination = { _ in
ProgressCenter.removeObserver(token)
}
}
}
// 使用
Task {
for await p in makeProgressStream() {
progressView.progress = Float(p)
}
}
自己动手:一个固定容量的 RingBuffer
需求:保持最新 N 条日志,支持 for-in 打印。
swift
struct RingBuffer<Element>: Collection {
private var storage: [Element?] // 用 Optional 占位
private var head = 0
private var tail = 0
private(set) var count = 0
private let capacity: Int
init(capacity: Int) {
self.capacity = capacity
storage = Array(repeating: nil, count: capacity)
}
// 写入新元素,覆盖最旧数据
mutating func append(_ newElement: Element) {
storage[tail] = newElement
tail = (tail + 1) % capacity
if count == capacity {
head = (head + 1) % capacity // 丢弃最旧
} else {
count += 1
}
}
// MARK: Collection 必备
typealias Index = Int
var startIndex: Int { 0 }
var endIndex: Int { count }
func index(after i: Int) -> Int {
precondition(i < endIndex, "Index out of bounds")
return i + 1
}
subscript(position: Int) -> Element {
precondition((0..<count).contains(position), "Index out of bounds")
let offset = (head + position) % capacity
return storage[offset]!
}
}
// 使用
var buffer = RingBuffer<Int>(capacity: 3)
for i in 1...5 {
buffer.append(i) // 1,2,3 → 2,3,4 → 3,4,5
}
for value in buffer {
print(value) // 3 4 5
}
总结与扩展场景
- 协议层次
IteratorProtocol → Sequence → Collection → BidirectionalCollection → RandomAccessCollection
每一层只加必要约束,绝不多要一颗糖。
- 值语义是 Swift 迭代的灵魂
结构体迭代器复制即"新游标",避免共享状态,这点与 Objective-C 的 NSEnumerator 形成鲜明对比。
- 遍历同时修改的崩溃本质是"迭代器失效"
所有带指针/索引的集合都存在,掌握"先记录后改"或"一次性 API"即可。
- AsyncSequence 让"事件流"变成普通 for-in
网络下载、蓝牙数据、用户点击序列都能用同一套思维建模;配合 AsyncStream 几乎零成本桥接老代码。
- 自定义 Collection 是架构试金石
RingBuffer 这类小容器写一遍,你会深刻理解"下标换算"、"容量与 count 区别"、"前置条件断言"这些日常被标准库隐藏的细节。