文章的展示内容不全,仅做简单了解。
一、什么是 KeyPath
KeyPath 是一个能让你表达属性位置的语法,通过 KeyPath 可以写出更弹性、更动态的语法。
struct User {
var name: String
}
let users = [User(name: "张三"), User(name: "李四")]
print(users.map { $0.name })
// 改用 KeyPath 来写(简写)
// 类似 Java/Kotlin 中的 User::name
print(users.map(\.name))
- 不需要实例就能表示
属性位置
和类型
。 - 类似于 Java/Kotlin 中的
User::name
。
二、KeyPath 组成
KeyPath 由下述格式组成:
\
+ 类型名称
+ .
+ 属性名称
// KeyPath<User, String>
\User.name
// 其中 User 是 Root,name 是 Value
三、KeyPath 使用
1. 用下标存取
let user = User(name: "张三")
// 两种写法存取的内容是一样的
user.name == user[keyPath: \.name]
使用场景:
假设现在有一个 HasName
的 Protocol,以及一个 User
和 Car
的 struct。
protocol HasName {
var name: String { get }
}
struct User: HasName {
let name: String
}
struct Car: HasName {
let name: String
let model: String
}
可以发现在 Car
中,name 属性应该被称为 brand 品牌更合适, 但是这里 Car
为了去 Conforms HasName
,就被限制了 Car
的属性名称一定要叫 name。
但是对 HasName
这个 Protocol 而言,它其实并不在乎这个属性名具体要叫什么,它只需要知道这个属性在哪里而已,所以这个 Protocol 其实可以改为使用 KeyPath 的方式来写。如下所示:
protocol HasName {
static var namePath: KeyPath<Self, String> { get }
}
struct User: HasName {
static let namePath = \User.name
let name: String
}
struct Car: HasName {
static let namePath = \Car.brand
let brand: String
let model: String
}
// 读取 name
extension HasName {
func greeting() {
print("Hello \(self[keyPath: Self.namePath])")
}
}
这样改写后,User
和 Car
可以有自己的独特的属性名称,同时不影响 greeting
正常读取到各自自定义的属性名称。
2. 转换闭包
struct User {
var name: String
}
let users = [User(name: "张三"), User(name: "李四"), ...]
let names = users.map(\.name)
四、五种 KeyPath 对比
KeyPath | WritableKeyPath | ReferenceWritableKeyPath | PartialKeyPath | AnyKeyPath | |
---|---|---|---|---|---|
Root 类型 | 任何指定类型 | 指定 Value Type 类型 | 指定 Reference Type 类型 | 任何指定类型 | 任何类型 |
Value 属性 | 指定类型 | 指定类型 必须是 Var |
指定类型 必须是 Var |
任何属性 | 任何属性 |
可编辑 | ❌ | ✅ | ✅ | ❌ | ❌ |