这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战
引言
在 Swift 中,有一块内容,我一直都不太愿意去学,那就是全系列的集合类型。那现在为啥去看了呢,因为卷 主要原因是标准库中提供的具体实现--数组、集合和字典--能覆盖日常开发 99% 的情况。还有一个更重要的原因----协议太多了!
更不用说 Swift 5.5 了。介绍了一个全新的基于 AsyncSequence 的协议家族,不过这篇文章中不会涉及。
那就让我们试着弄清楚每个协议的作用,以及为什么我们需要这么多协议。下面的图就展示了全系列的集合协议,我将从上到下依次介绍:
IteratorProtocol
一次提供一个序列值的类型。该协议与 Sequence 协议紧密相连,后面就会看到。序列通过创建迭代器来提供对其元素的访问,迭代器跟踪其迭代过程,并随着序列的前进每次返回一个元素。协议的定义如下:
public protocol IteratorProtocol {
associatedtype Element
mutating func next() -> Element?
}
一个名为 next() 的方法,返回下一个元素或 nil。它被标记为 mutating,这样就可以更新他们的内部状态,为下一次调用 next() 做准备。如果实现没有返回 nil,迭代器可以无限地生成值。我们很少直接创建迭代器,因为 Swift 中的序有一种更为常用的方法。不过,让我们创建一个具体的迭代器来看看它是如何工作的。
struct DoublingIterator: IteratorProtocol {
var value: Int
var limit: Int? = nil
mutating func next() -> Int? {
if let l = limit, value > l {
return nil
} else {
let current = value
value *= 2
return current
}
}
}
var doublingIterator = DoublingIterator(value: 1, limit: 1024)
while let value = doublingIterator.next() {
print(value)
}
调用一个简单的迭代器,每次调用 next(),值增加一倍。如果我们初始化 DoublingIterator 时,limit 传 nil,迭代器将永远运行,直到值超出最大值。
Sequence
可以用对其元素进行顺序迭代访问的类型。序列就是一系列的值,每次我们都可以访问一个。虽然看起来很简单,但是这种能力使我们能够进行大量的操作,而且可以对任何序列执行这些操作。Sequence 协议为这些常见操作提供了缺省实现。在研究这些操作之前,让我们先看一下协议定义~
public protocol Sequence {
associatedtype Element
associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
func makeIterator() -> some IteratorProtocol
}
序列通过它的 makeIterator() 方法和关联的类型来确保迭代器的 Element 类型和序列匹配。让我们使用 DoublingIterator 创建一个具体的 DoublingSequence:
struct DoublingSequence: Sequence {
var value: Int
var limit: Int? = nil
func makeIterator() -> DoublingIterator {
DoublingIterator(value: value, limit: limit)
}
}
let doubler = DoublingSequence(value: 1, limit: 1024)
for value in doubler {
print(value)
}
print(doubler.contains { $0 == 512 }) // true
print(doubler.reduce(0, +)) // 2047
仅仅通过遵循 Sequence,我们的具体类型就获得了 for-in 和一些其他操作的能力,如 map、 filter、 reduce 等。Sequence 还提供了 dropFirst(_:)、 dropLast(_:) 等方法。但是,在序列级别,这些方法的实现受到一次迭代一个元素的约束。这反映了它们的时间复杂度—— dropFirst(_:)是 O(k),其中 k 是要删除的元素数,dropLast(_:) 是 O(n),其中 n 是按顺序排列的元素总数。dropLast(_:) 与 dropFirst(_:)不同,它要求序列是有限的。
在使用 Sequences 时需要注意一些事情
- 序列不能保证多次迭代产生想要的结果。由实现类型决定如何处理对已经遍历过一次的序列的迭代
- 序列提供的迭代器应保证时间复杂度为 O(1)。它对元素访问没有其他要求。因此,除非另有文档说明,否则应该将遍历序列的方法视为 O(n)
给定一个元素,Sequence 允许我们移动到下一个元素。为了能够移动到任何元素(虽然不能保证移动的世界是常量时间),我们需要 Collection。
Collection
一个序列,其元素可以通过下标被多次访问。当我们使用数组、字典或集合时,受益于 Collection 协议声明和实现的操作。除了从 Sequence 协议继承的操作之外,我们还可以访问集合中特定位置的元素的方法。Collection 的协议定义如下:
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
}
由于多次遍历和通过索引下标访问的需要,一个集合不能延迟地计算它的值,也不能是无限的。这与 Sequences 不同,Sequences 可以通过当前对 next() 的调用更新内部状态,为下一次调用做准备。还要注意,associatedtype Index 不是 Int 类型,而是符合 Comparable 的任何类型。
结语
在下篇文章中,我们继续探索剩余的部分~