Swift 键路径

2022/9/10 SwiftiOS

键路径是一个指向属性的未调用的引用。它描述了一个从根路径开始的类型层级路径。代码示例如下:

struct Address {
	var street: String
	var city: String
	var zipCode: Int
}

struct Person {
	let name: String
	var address: Address
}

// WritableKeyPath<Person, String>
let streetPath = \Person.address.street

// KeyPath<Person, String>
let namePath = \Person.name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

键路径可以由任意的存储和计算属性组合而成,其中还可以包括可选链操作符。编译器会自动为所有类型生成 [keyPath:] 的下标方法。你通过这个方法来“调用”某个键路径。对键路径的调用,也就是在某个实例上访问由键路径所描述的属性。

let address = Address(street: "成华大道", city: "成都", zipCode: 610081)
print(address[keyPath: \.street]) // 成华大道

1
2
3

namePath 的类型是KeyPath<Person, String>,这个键路径是强类型的,它表示该键路径可以作用于 Person,并返回一个 String。而 streetPath 是一个WritableKeyPath,这是因为构成这个键路径的所有属性都是可变的,所以这个可写键路径本身允许其中的值发生变化。

let streetPath = \Person.address.street
var person = Person(name: "Feliks", address: address)
person[keyPath: streetPath] = "二仙桥"
1
2
3

也可以使用 KeyPath 来描述下标操作,例如提取数组中元素的属性:

var lisa = Person(name: "lisa", address: address)
var bart = Person(name: "bart", address: address)

let people = [lisa, bart]
print(people[keyPath: \.[1].name])
1
2
3
4
5

也可以用于在键路径中包含字典下标。

# KeyPath Can Be Modeled with Functions

编译器可以自动把一个键路径表达式转换成一个函数,例如在 map 中的使用:

people.map { $0.name }
people.map(\.name)
1
2

这只对键路径表达式有效,以下代码会编译失败。

let keyPath = \Person.name
people.map(keyPath)
1
2

直接传不行,因为类型不匹配。为了解决这个问题,可以显式地转换成函数形式。

let f: (User) -> String  = \.name
let f2 = \.name as (User) -> String
1
2

编译器会生成类似的函数:

let f: (User) -> String = { kp in { root in root[keyPath: kp] } }(\User.name)
1

键路径可以附加,但是类型必须匹配,可以实现

A->B + B->C = A->C

// KeyPath<Person, String> + KeyPath<String, Int> = KeyPath<Person, Int>
let nameCountKeyPath = nameKeyPath.appending(path: \.count)
// Swift.KeyPath<Person, Swift.Int>
1
2
3
Last Updated: 2022/9/11 17:39:53