集合类型模块分四篇笔记来学习:
- 第一篇:
- 数组和可变性
- 数组的变形
- 第二篇:
- 字典和集合
- 集合协议
- 第三篇:
- 集合
- 第四篇:
- 索引
本篇开始学习第二篇,Go!
1.字典和集合
Swift 中另一个关键的数据结构是字典。字典指的是包含键以及它们所对应的值的数据结构。在一个字典中,每个键都只能出现一次。
举个例子,我们现在来为设置界面构建一些基础代码。我们先定义了 Settings 协议,遵守这个协议的类型需要提供一个能够设定它的 UIView 对象。比如对于 String,我们返回 UITextField,而对于布尔值,我们返回 UISwitch:
protocol Setting {
func settingsView() -> UIView
}
现在我们可以定义一个由键值对组成的字典了。这个字典中的键是设置项的名字,而值就是这些设定所存储的值。我们使用 let 来定义字典,也就是说,我们之后不会再去更改它。
let defaultSettings: [String:Setting] = [
"Airplane Mode": true,
"Name": "My iPhone",
]
变更
和数组一样,使用 let 定义的字典是不可变的:你不能向其中添加或者修改条目。如果想要定义一个可变的字典,你需要使用 var 进行声明。想要将某个值从字典中移除,可以将它设为 nil。
有用的字典扩展
我们可以自己为字典添加一些扩展,比如增加将两个字典合并的功能。
举例来说,当我们将设置展示给用户时,我们希望将它们与用户存储的设置进行合并后再一起展示。假设我们已经有一个 storedSettings 方法来读取用户所存储的设置:
现在,我们来为字典增加一个 merge 函数。字典遵守 SequenceType 协议,为了通用性,我们可以让这个方法接受的参数是任意的遵守 SequenceType 协议、并且能产生 (Key,Value) 键值对的类型。这样一来,我们将不仅能够合并字典类型,也能对其他类似的类型进行相同的操作。
extension Dictionary {
mutating func merge<S: SequenceType
where S.Generator.Element == (Key,Value)>(other: S) {
for (k, v) in other {
self[k] = v
}
}
}
现在我们可以将存储的设置与默认设置进行合并了:
var settings = defaultSettings
settings.merge(storedSettings())
另一个有意思的扩展是从一个 (Key, Value) 键值对的序列来创建字典。我们可以先创建一个空字典,然后将序列合并到字典中去。这样一来,我们就可以重用上面的 merge 方法,让它来做实际的工作:
extension Dictionary {
init<S: SequenceType where S.Generator.Element == (Key,Value)>(_ sequence: S) {
self = [:]
self.merge(sequence)
}
}
// 所有 alert 默认都是关闭的
let defaultAlarms = (1..<5).map { ("Alarm \($0)", false) }
let alarmsDictionary = Dictionary(defaultAlarms)
在闭包中使用集合
在 Swift 标准库中,还包含了 Set 类型。Set 是一系列元素的无序集合,每个元素在 Set 中只会出现一次。Set 遵循 ArrayLiteralConvertible 协议,也就是说,我们可以通过使用和数组一样的字面量语法来初始化一个集合。
let mySet: Set<Int> = [1, 2]
在你的函数中,无论你是否将它们暴露给调用者,字典和集合都会是非常有用的数据结构。举个例子,如果我们想要为 SequenceType 写一个扩展,来获取序列中所有的唯一元素,我们只需要将这些元素放到一个 Set 里,然后返回这个集合的内容就行了。不过,因为 Set 并没有定义顺序,所以这么做是不稳定的,输入的元素的顺序在结果中可能会不一致。为了解决这个问题,我们可以创建一个扩展来解决这个问题,在扩展方法内部我们还是使用 Set 来验证唯一性:
extension SequenceType where Generator.Element: Hashable {
func unique() -> [Generator.Element] {
var seen: Set<Generator.Element> = []
return filter {
if seen.contains($0) {
return false
} else {
seen.insert($0)
return true
}
}
}
}
上面这个方法让我们可以找到序列中的所有不重复的元素,并且维持它们原来的顺序。
2.集合协议
留心看看数组、字典以及集合的定义,我们会发现它们都遵守 CollectionType 协议。更进一步,CollectionType 本身是一个遵守 SequenceType 的协议。而序列 (sequence) 使用一个 GeneratorType 来提供它其中的元素。简单说,生成器 (generator) 知道如何产生新的值,序列知道如何创建一个生成器,而集合 (collection) 为序列添加了有意义的随机存取的方式。下面开始逆着学习。
生成器
GeneratorType 协议通过两部分来定义。
- 它要求遵守它的类型都需要有一个 Element 关联类型,这个类型也就是生成器产生的值的类型。(在数组中,生成器的元素类型就是数组的元素类型。在字典中,元素类型是一个同时包含键和值的多元组,也就是 (Key, Value))
- GeneratorType 定义的第二部分是 next 函数,它返回类型为 Element 的可选值。对于遵守 GeneratorType 的值,你可以一直调用它的 next 函数,直到你得到 nil 值。
结合这两部分,我们就能得到 GeneratorType 协议的定义了:
protocol GeneratorType {
associatedtype Element
mutating func next() -> Element?
}
你只能将生成器的值循环一次。也就是说,生成器并没有值语义。所以我们将会使用 class 来创建生成器,而不使用 struct
我们能写出的最简单的生成器是一个在被询问下一个值时每次都返回常数的生成器:
class ConstantGenerator: GeneratorType {
typealias Element = Int
func next() -> Element? {
return 1
}
}
或者采用隐式的赋值:
class ConstantGenerator: GeneratorType {
func next() -> Int? {
return 1
}
}
声明一个斐波那契数的生成者:
class FibsGenerator: GeneratorType {
var state = (0, 1)
func next() -> Int? {
let upcomingNumber = state.0
state = (state.1, state.0 + state.1)
return upcomingNumber
}
}
上面这些生成器都能且只能被迭代一次。如果我们想要再次进行迭代,那么就需要创建一个新的生成器。这也通过它的定义有所暗示:我们将它们声明成了类而非结构体。在被不同变量共享的时候,只有引用的传递,而不发生复制。
Swift 标准库中的 AnyGenerator 有着曲折的过去。Swift 最开始的时候曾经有个叫做 GeneratorOf 的类型,它是一个结构体,做着与现在 AnyGenerator 类似的事情。在 Swift 2.0 的时候,AnyGenerator 取代了 GeneratorOf,并且它是一个类。不过在 Swift 2.2 里,它又变成了一个结构体。虽然 AnyGenerator 现在是一个结构体,但是它并不具有值语义,因为它其实将生成器的实现封装到了一个方法的引用类型中去。作为存在状态的结构,生成器其实在 Swift 标准库中显得有那么一点格格不入,因为标准库里绝大部分类型都是具有值语义的结构体。
避免去复制生成器。当你需要生成器的时候,总是去创建一个新的。我们接下来要说的序列类型 (SequenceType) 就很好地遵守了这一原则。
序列
进行多次迭代是很常见的行为,因此 SequenceType 应运而生。SequenceType 是构建在 GeneratorType 基础上的一个协议,它需要我们指定一个特定类型的生成器,并且提供一个方法来创建新的生成器:
protocol SequenceType {
associatedtype Generator: GeneratorType
func generate() -> Generator
}
举个例子,想要多次枚举前面我们提到的前缀的话,我们可以把 PrefixGenerator 放到一个序列中去。我们在这里没有显式地指定 GeneratorType 的类型,通过读取 generate 方法的返回类型,编译器可以为我们推断出所需要的 GeneratorType:
struct PrefixSequence: SequenceType {
let string: String
func generate() -> PrefixGenerator {
return PrefixGenerator(string: string)
}
}
因为我们实现了 SequenceType 协议,我们现在已经可以用 for 循环来迭代给定字符串的所有前缀了:
for prefix in PrefixSequence(string: "Hello") {
print(prefix)
}
for 循环的工作原理是这样的:编译器为序列创建一个新的生成器,然后一直调用这个生成器的 next 方法获取其中的值,直到它返回 nil。从本质上说,for 就是下面这段代码的一种简写:
var generator = PrefixSequence(string: "Hello").generate()
while let prefix = generator.next() {
print(prefix)
}
还有一种更简单的方法来创建生成器和序列。相比于创建一个新的类,我们可以使用 Swift 内建的 AnyGenerator 和 AnySequence 类型。它们接受一个函数作为参数,并根据这个函数为我们构建相应的类型。示例如下:
func fibGenerator() -> AnyGenerator<Int> {
var state = (0, 1)
return AnyGenerator {
let result = state.0
state = (state.1, state.0 + state.1)
return result
}
}
通过生成器创建序列的更加容易:
let fibSequence = AnySequence(fibGenerator)
网友评论