第三章——集合(集合协议)

112 阅读5分钟

只要仔细观察一下数组、字典、集合(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协议。

###基于函数的生成器和序列

生成序列和生成器还有一种更简单的方式,这种方式不用创建一个自定义的类,而是使用内建的AnyGeneratorAnySequence类型。这两种类型需要接受一个函数作为参数。比如我们可以这样实现之前的斐波那契数列生成器:

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)
}