只要仔细观察一下数组、字典、集合(Set)和字符串视图,我们可以发现他们都实现了CollectionType
协议。如果再深入一些,CollectionType
协议实现的是SequenceType
协议,同时序列(Sequence)使用了GeneratorType
来提供元素。为了了解这其中的详细原理,我们从最底层往上看。简单来说,一个生成器(Generator)负责封装如何生成新的值的细节,序列(Sequence)封装了创建生成器(Generator)的细节,而集合类型(Collection)可以随机地访问序列(Sequence)。
###生成器(Generators)
GeneratorType
协议由两部分组成。首先,它要求实现这个协议的类型有一个关联的Element
类型。比如考虑String.CharacterView
,它的元素类型(element type)就是Character
,对于字典来说,它的元素类型被定义为(Key, Value),也就是我们所说的键值对。
GeneratorType
协议还定义了next()
函数,它返回的是元素的可选类型值。所以只要你有一个实现了GeneratorType
协议的值,你就可以调用它的next()
方法,直到这个方法返回nil
为止。把刚刚所说的GeneratorType
协议的两部分合并起来,我们就得到了它简单的定义:
protocol GeneratorType {
typealias Element
mutating func next() -> Element?
}
Swift文档告诉我们我们可以随便赋值生成器类型的值,但由于生成器只能单向访问:我们只能遍历它一次。因此生成器没有值语义,所以我们需要把它实现为一个类而不是结构体。我们可以想到的最简单的生成器是一个在next()
方法中总是返回常量的对象:
class ConstantGenerator: GeneratorType {
typealias Element = Int
func next() -> Element? {
return 1
}
}
或者我们也可以利用类型推断,隐式地标注出Element的类型:
class ConstantGenerator: GeneratorType {
func next() -> Int? {
return 1
}
}
这个生成器的使用很简单,只要先创建它的实例对象,用一个while
循环,就可以获得无数个1
:
var generator = ConstantGenerator()
while let x = generator.next() {
// 使用x
}
如果要生成一个斐波那契数列(从0开始),我们需要额外保存一个状态,也就是当前数字的前两个数。这时next()
函数返回的是前两个数之和,同时还要更新这个状态:
class FibsGenerator: GeneratorType {
var state = (0,1)
func next() -> Int? {
let upcommingNumber = state.0
state = (state.1, state.0 + state.1)
return upcommingNumber
}
}
刚刚我们举的两个生成器的例子,都是可以生成无限多值的。再来看一个有限的生成器PrefixGenerator
,它可以生成字符串的所有前缀子串(包括字符串自己),它内部记录一个每次自增的下标,返回的就是从0到这个下标之间的子字符串:
class PrefixGenerator: GeneratorType {
var string: String
var offset: String.Index
init (string: String) {
self.string = string
offset = string.startIndex
}
func next() -> String? {
if offset < string.endIndex {
offset++
return string[string.startIndex..<offset]
}
return nil
}
}
以上所示的这三个生成器都只能遍历一次。如果还想从头开始再遍历一次,就得重新创建一个生成器。因为我们知道生成器不是结构体而是类,它们被另一个变量共享时,不会复制一个新的而是直接传递引用。
###序列(Sequences)
我们知道生成器只能遍历一次,但多次遍历又是一个很常见的需求,所以就有了SequenceType
这个协议。它是基于GeneratorType
的。在这个协议中,我们需要指定生成器的类型,并且提供一个可以创建生成器的函数。定义在这个协议中的typealias
关键字就表示需要指定的生成器的类型:
protocol SequenceType {
typealias Generator: GeneratorType
func generate() -> Generator
}
这样一来,如果想要多次遍历字符串的所有前缀,我们就可以把生成器包装在序列中:
struct PrefixSequence: SequenceType {
let string: String
func generate() -> PrefixGenerator {
return PrefixGenerator(string: string)
}
}
现在,我们已经把创建生成器的过程封装在序列中,由PrefixSequence
负责,于是我们就可以在for
循环里遍历所有的字符串前缀了:
for prefix in PrefixSequence(string: "Hello") {
print(prefix)
}
隐藏在简单的for
循环背后的是编译器创建了一个全新的生成器,然后不断调用生成器的next
方法直到得到返回值为nil
。
如果我们去看看Swift对数组的实现,就更容易想象生成器和序列具体是什么,其他的数据结构如字典、集合、字符串也是如此。如果我们想要实现自己的数据结构并想让它可以在for
循环中使用,就需要保证它实现了SequenceeType
协议。
###基于函数的生成器和序列
生成序列和生成器还有一种更简单的方式,这种方式不用创建一个自定义的类,而是使用内建的AnyGenerator
和AnySequence
类型。这两种类型需要接受一个函数作为参数。比如我们可以这样实现之前的斐波那契数列生成器:
func fibGenerator() -> AnyGenerator<Int> {
var state = (0,1)
return anyGenerator {
let result = state.0
state = (state.1, state.1 + state.0)
return result
}
}
anyGenerator
方法把另一个函数作为参数(也就是定义中的body函数)。在这个例子中,我们把state
移到函数之外,然后每次函数调用时都该state
。因为函数会截获自动变量,所以这么做是没有问题的。
现在,创建序列就更简单了:
let fibSequence = AnySequence(fibGenerator)
for number in fibSequence {
print(number)
}