Swift中另一个关键的数据结构是字典。一个字典由键和与键对应的值组成,每个键在数组中必须是唯一的。接下来我们会构造一个setting页面的底层实现。在开始前,我们首先定义一个Setting
协议。任何提供了UIView
来展示setting页面的类型都可以遵守这个协议。如果是String
类型,就提供UITextField
,如果是布尔类型,就提供UISwitch
。
protocol Setting {
func settingsView() -> UIView
}
复制代码
现在我们来定义一个字典。其中的键表示设置的名字,值就是设置的值。我们用let
关键字来定义字典,这就说明以后不能再修改它了:
let defaultSettings: [String: Setting] = [
"Airplane Mode": true,
"Name": "My iPhone",
]
复制代码
我们需要用字典的下标脚本(比如defaultSettings["Name"])来获取setting的值。这种查找字典的方式总是会返回可选类型的结果,如果一个键在数组中没有对应的值,就会返回nil
。相比之下,如果下标越界数组会直接崩溃[1]。产生这样区别的原因在于,字典中的数据是松散的(相对于key来说),而数组的下标总是连续的。在本章的末尾我们会深入的讨论这种权衡。目前,我们需要明白Swift会把可能为nil
的值(可选值)和不可能为nil
的值(普通值)严格区分开,这在可选类型章节会有更详细的讨论。
###变异(Mutation)
和数组一样,用let
定义的字典是不可变的,既不能增删数据也不能修改某一条键值对。同样地,我们可以用var
关键字来定义字典。如果想删除字典中的数据,只要把它置为nil
即可。如果想修改不可变的数组,则需要把它先拷贝一份:
var localizedSettings = defaultSettings
localizedSettings["Name"] = "Mein iPhone"
localizedSettings["Do Not Disturb"] = true
复制代码
再次强调,对localizedSettings
的修改不会影响到defaultSettings
。除了用下标脚本外,还可以用updateValue
方法修改数组,这个方法的返回值是修改前的值。
let oldName = localizedSettings.updateValue("My iPhone", forKey: "Name")
// oldName = "Mein iPhone"
复制代码
Swift还有一种内建的集合(collection)类型是集合(Set)[2]。集合(Set)可以被桥接转化成NSSet
类型。其他的集合类型有Range
,表示一段连续下标的范围,Repeat
是包含了多个重复数据的集合。甚至,可选类型Optional
都可以理解为含有最多一个元素的集合。
###一些有用的数组拓展
我们可以自己写一个很有用的数组拓展,实现把两个字典合并的功能。比如在把settings展示给用户时,我们希望合并用户已经排序的setting。用storedSettings
函数来读取setting:
func storedSettings() -> [String: Setting]
复制代码
我们可以给字典添加一个merge
方法。我们知道字典遵守SequenceType
协议,所以为了让这个方法更加普适,不仅可以合并字典,还能合并其他实现了SequenceType
协议并能生成键值对的类型,merge
方法可以这样实现:
extension Dictionary {
mutating func merge (other: S) {
for (k,v) in other {
self[k] = v
}
}
}
复制代码
于是合并字典可以这么写:
var settings = defaultSettings.merge(sortedStrings())
复制代码
另一个很有趣的拓展是把一系列(key,value)类型的值转换成字典。我们可以从一个空字典开始,然后不断把键值对合并进来。这会用到之前定义的merge
方法来处理比较繁琐的合并步骤:
extension Dictionary {
init<S: SequenceType
where S.Generator.Element == (Key,Value)> (_ sequence: S) {
self = [:]
self.merge(sequence)
}
}
//所有闹钟默认出于关闭状态
let defaultAlarms = [1..<5].map { ("Alarm \($0)", false) }
let alarmsDictionary = Dictionary(defaultAlarms)
复制代码
如果你对数组的map
方法不熟悉,请查看上一节——《数组变换》
最后一个常用的拓展是字典的map
方法。因为字典本身就是一个序列,实现了SequenceType
,所以它有自己默认的map
方法可以生成一个数组。但有时候我们希望能够保留字典结构,只是遍历value进行变换。我们可以写一个mapValue
方法[3]。注意不能对整个数组进行map
,否则有可能会有重复的键:
extension Dictionary {
func mapValues (transform: Value -> NewValue) -> [Key:NewValue] {
return Dictionary<Key,NewValue>( map { (key,value) in
return (key, transform(value))
})
}
}
let keysAndViews = settings.mapValues { $0.settingsView() }
复制代码
###在闭包中使用数组和集合
这一节预览版里只有两段代码,正式版中应该是对unique
和frequence
方法的介绍,期待正式版:
extension SequenceType where Generator.Element: Hashable {
func unique() -> [Generator.Element] {
var seen: Set<Generator.Element> = []
return self.filter {
if seen.contains($0) {
return false
}
else {
seen.insert($0)
return false
}
}
}
func frequence() -> [(Generator.Element, Int)] {
var frequency: [Generator.Element:Int] = [:]
for x in self {
frequency[x] = (frequency[x] ?? 0) + 1
}
return frequency.sort { $0.1 > $1.1 }
}
}
复制代码
#译者注
[1]:在已经开源的swift源码中,我发现一个针对NSArray数组下标脚本的更改,而且合并申请已经被通过。也就是说也许在Swift3里面,NSArray的下标脚本也是安全的了,不过也不是返回nil
。文档地址,在大约340行左右的位置,或者搜索“subscript”关键字。
[2]:此集合非彼集合,前者是collection,表示广义上的数据聚集。后者是Set,更加偏向于数学概念上的集合,相比如数组而言,强调数据的无序性。
[3]:注意这里用到了之前拓展的字典的初始化方法,根据一个(key,value)数组来创建字典。