swift字典简介

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情

特征

字典典是无序的,使用 for 循环来枚举字典中的键值对时,顺序是不确定的

一个app的设置界面,使用enum来表示

enum Setting {
  case text(String)
  case int(Int)
  case bool(Bool)
}
复制代码
let defaultSetting: [String: Setting] = [
  "Open": .bool(false),
  "Name": .text("My iPhone")
]
defaultSetting["Name"]
复制代码

通过字典查找的值总是返回一个可选值,当它不存在时,它就会返回nil.数组越界则会崩溃

“与通常使用的以键为参数的下标方法形成对比的是,作为实现 Collection 协议的一部分,字典也有一个接受索引的下标方法。就像数组一样,当用一个无效的索引调用这个方法时,程序会崩溃”

可变性

你可以向var定义的可变字典移除一个值,通过用下标将对应的值设置为nil,要么调用removeValue(forKey:) 。后一种方法还会将被删除的值返回 (如果待删除的键不存在,则返回 nil)。对于一个用let定义的不可变的字典,想要进行修改的话,我们需要进行复制

var userSettings = defaultSettings
userSettings["Name"] = .text("Jared's iPhone")
userSettings["Do Not Disturb"] = .bool(true)
复制代码

defaultSettings在这里没有过改变,也可以用updateValue(_:forKey:) 更新值,这个方法会返回之前的值

一些有用的字典方法

merge(_:uniquingKeysWith:)

合并俩个字典

var settings = defaultSettings
let overriddenSettings: [String:Setting] = ["Name": .text("Jane's iPhone")]
settings.merge(overriddenSettings, uniquingKeysWith: { $1 })
settings
// ["Airplane Mode": Setting.bool(false), "Name": Setting.text("Jane's iPhone")]
复制代码

mapValues

对值进行变换

let settingsAsStrings = settings.mapValues { setting -> String in
switch setting {
case .text(let text): return text
case .int(let number): return String(number)
case .bool(let value): return String(value)
}
}
settingsAsStrings // ["Airplane Mode": "false", "Name": "Jane's iPhone"]”

摘录来自: Chris Eidhof. “Swift 进阶Apple Books. 
复制代码

{ $1 } 来作为合并两个值的策略

Hashable要求

let settingsAsStrings = settings.mapValues { setting -> String in
    switch setting {
        case .text(let text): return text
        case .int(let number): return String(number)
        case .bool(let value): return String(value)
    }
}
settingsAsStrings // ["Airplane Mode": "false", "Name": "Jane's iPhone"]
复制代码
  • 字典其实就是哈希表,字典通过键的hashValue来为每个键在其底层作为数组指定一个位置
  • 字典的key 要求遵守Hashable协议
  • 标准库中基本所有数据类型都是遵守这个Hashable协议的,包括字符串,整形,浮点型,布尔值,不带有关联类型的枚举也是自动遵守Hashable.另外像数组,集合和可选值这些类型,如果他们的元素是可哈希的,那么他们会自动称为可哈希值。
  • 对于枚举和结构体,只要它们是由可哈希类型组成,那么Swift会自动帮我们合成Hashable协议所需要的实现。如果枚举包含关联值是可哈希的,那么这个枚举就是可哈希的,如果没有关联值,那么就会自动实现Hashable协议。结构体的所有存储属性是可哈希的,那么也就不用手动实现Hashable协议.
  • 如果不能自动实现Hashable协议(要么因为你正在实现一个类;要么出于哈希的目的,在自定义结构体有几个属性需要忽略),首选你需要让类型实现Equatable协议,然后实现func hash(into hasher: inout Hasher) 方法,(Swift4.2通过实现hashValue属性的方法已经被弃用)。这个方法接收Hasher类型参数
    在Swift4.1中hashValue的实现swift
// Swift 4.1
struct Person: Hashable {
  var age: Int
  var name: String

  var hashValue: Int {
     return age.hashValue ^ name.hashValue &* 16777619
  }
}
复制代码

首先 ^ 是异或,&* 是防止乘法溢出 crash 的运算符,16777619 显然也不是一个随便选择的数字

在Swift4.2中简化为

// Swift 4.2
struct Person: Hashable {
  var age: Int
  var name: String

func hash(into hasher: inout Hasher) {
  hasher.combine(age)
  hasher.combine(name)
  }
}
复制代码
  • 你应该用相同组件进行相等性检查,因为必须遵守以下不变的原则:俩个相同的实现(由你实现的==),必须拥有相同的哈希值,反过来不一定相等,不同的哈希值数量有限,然而被哈希的类型个数是无穷的

标准库的通用哈希函数使用一个随机种子作为其输入之一。也就是说,字符串 "abc" 的哈希值在每次程序执行时都会是不同的。随机种子是一种用来防止有针对性的哈希洪泛式拒绝服务攻击的安全措施。因为字典和集合是按照存储在哈希表中的顺序来迭代它们的元素,并且由于这个顺序是由哈希值决定的,所以这意味着相同的代码在每次执行时会产生不同的迭代顺序。如果你需要哈希值每次都一样,例如为了测试,那么可以通过设置环境变量SWIFT_DETERMINISTIC_HASHING=1 来禁用随机种子,但是你不应该在正式环境中这么做.
注:对于非值语义类型.例如可变对象作为字典的键时,需要特别小心,因为它的值改变后,就无法在字典中找到它。

猜你喜欢

转载自juejin.im/post/7086762197061206030
今日推荐