可写键路径与引用键路径
通过键路径读取和写入值。
可写键路径与引用键路径 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 4 节课。
只读键路径与可写键路径
普通的 KeyPath 只能读取值。要通过键路径写入值,您需要使用可写变体。Swift 提供了两种可写键路径,一种用于值类型,另一种用于引用类型。
本课将介绍用于结构体的 WritableKeyPath、用于类的 ReferenceWritableKeyPath,以及如何让 [keyPath:] 下标变为可设置的。
struct Person {
var name: String
var age: Int
}
let writable: WritableKeyPath<Person, Int> = \Person.age
print(type(of: writable))用于值类型的 WritableKeyPath
WritableKeyPath 指向值类型的可变存储属性。要通过它进行设置,实例本身必须是 var,因为修改结构体属性会修改整个值。
请将 [keyPath:] 下标放在赋值语句的左侧,以执行写入。
struct Person {
var name: String
var age: Int
}
var alice = Person(name: "Alice", age: 30)
let agePath = \Person.age
alice[keyPath: agePath] = 31
print(alice.age)为什么实例必须是 var
对于值类型,通过键路径写入会在原位置修改一个副本。如果实例是 let,就没有可供修改的对象,因此赋值无法通过编译。
这条规则与普通的属性修改一致:只有当结构体保存在变量中时,才能设置它的存储属性。
struct Point {
var x: Int
var y: Int
}
var p = Point(x: 1, y: 2)
p[keyPath: \Point.x] = 10
p[keyPath: \Point.y] = 20
print(p)通用设置辅助函数
由于可写键路径记录了要修改的属性,因此您可以编写一个通用函数,更新任意值类型的任意属性。请将实例以 inout 形式传入,以便将更改写回。
这构成了小型函数式更新工具的基础。
func update<Root, Value>(_ root: inout Root, _ path: WritableKeyPath<Root, Value>, to value: Value) {
root[keyPath: path] = value
}
struct Person { var name: String; var age: Int }
var bob = Person(name: "Bob", age: 40)
update(&bob, \.age, to: 41)
print(bob.age)用于类的 ReferenceWritableKeyPath
对于类,Swift 提供了 ReferenceWritableKeyPath。由于类实例是引用,即使实例保存在 let 常量中,也可以通过它进行写入;发生变化的不是引用,而是引用所指向的对象。
这就是它与值类型可写键路径之间的关键区别。
class Counter {
var value: Int = 0
}
let counter = Counter()
let path: ReferenceWritableKeyPath<Counter, Int> = \Counter.value
counter[keyPath: path] = 5
print(counter.value)let 引用与可变对象
请注意,下面的类实例是 let,但我们仍然可以通过键路径设置它的属性。如果使用结构体,这将导致编译错误。
这是因为修改类属性不要求引用本身可变,只要求属性本身可修改。
class Profile {
var nickname: String = ""
}
let profile = Profile()
let nickPath = \Profile.nickname
profile[keyPath: nickPath] = "Ace"
print(profile.nickname)键路径的类型层级
可写键路径类型构成一个层级。ReferenceWritableKeyPath 是 WritableKeyPath 的子类型,而 WritableKeyPath 又是 KeyPath 的子类型。因此,在需要更通用的键路径时,可以使用更具体的键路径。
这意味着,您可以将 ReferenceWritableKeyPath 传递给只需要通过 KeyPath 进行读取的函数。
class Box { var size: Int = 1 }
func readSize(_ box: Box, _ path: KeyPath<Box, Int>) -> Int {
box[keyPath: path]
}
let writable: ReferenceWritableKeyPath<Box, Int> = \Box.size
print(readSize(Box(), writable))更新嵌套的值类型
可写键路径可以沿着嵌套结构体逐层连接。设置深层嵌套的属性时,会沿链路重写每个外层值,并通过一次赋值完成。
这里我们修改嵌套 address 中的城市。
struct Address { var city: String }
struct Person { var name: String; var address: Address }
var carol = Person(name: "Carol", address: Address(city: "Oslo"))
carol[keyPath: \Person.address.city] = "Bergen"
print(carol.address.city)应用多项更新
由于可写键路径本身也是值,您可以将一系列更新存储为键路径和值的组合,然后在循环中应用它们。这就是一个小型补丁系统。
这里我们对一个 settings 结构体应用两项更改。
struct Settings { var volume: Int; var brightness: Int }
var settings = Settings(volume: 5, brightness: 5)
func apply(_ s: inout Settings, _ path: WritableKeyPath<Settings, Int>, _ v: Int) {
s[keyPath: path] = v
}
apply(&settings, \.volume, 8)
apply(&settings, \.brightness, 3)
print(settings)绑定式配置
一个常见的实际用法是编写 configure 辅助函数:它会设置新创建对象的某个属性并返回该对象,从而支持流畅的调用风格。对于类,这种方式可以作用于 let 结果。
这里我们通过引用可写键路径设置 label 的文本。
class Label { var text: String = "" }
func configure<T: AnyObject, V>(_ object: T, _ path: ReferenceWritableKeyPath<T, V>, _ value: V) -> T {
object[keyPath: path] = value
return object
}
let label = configure(Label(), \.text, "Hello")
print(label.text)选择合适的键路径类型
只进行读取时使用 KeyPath;要设置保存在 var 中的值类型属性,使用 WritableKeyPath;即使通过 let 设置类的属性,也使用 ReferenceWritableKeyPath。
选择满足需求的最具体类型,可以让 API 准确体现自身是只读还是会修改数据。
struct S { var n: Int }
class C { var n: Int = 0 }
let read: KeyPath<S, Int> = \S.n
let writeStruct: WritableKeyPath<S, Int> = \S.n
let writeClass: ReferenceWritableKeyPath<C, Int> = \C.n
print(type(of: read), type(of: writeStruct), type(of: writeClass))快速检查:可写键路径
回想一下:即使类实例是 let 常量,哪种键路径类型仍能让您设置类属性?
回顾:可写键路径与引用键路径
您已经学会了如何通过键路径进行写入:
WritableKeyPath可以设置值类型的存储属性,但实例必须是var。ReferenceWritableKeyPath可以设置类的属性,即使通过let也可以使用。[keyPath:]下标出现在赋值语句的左侧,用于执行写入。- 这些类型构成一个层级,因此可写键路径可以代替只读键路径。
- 通用的
inout设置函数和配置辅助函数都建立在这些能力之上。
接下来您将看到 @dynamicMemberLookup,它利用键路径为动态数据提供点语法访问。
struct Person { var name: String; var age: Int }
var dave = Person(name: "Dave", age: 27)
dave[keyPath: \Person.age] = 28
print("Updated age: \(dave.age)")常见问题解答
「可写键路径与引用键路径」课时是免费的吗?
是的 — 「可写键路径与引用键路径」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Swift Academy 课程的其余内容,请升级到 CoddyKit PRO。 Swift Academy 课程共包含 4 节课。
「可写键路径与引用键路径」这节课中我会学到什么?
通过键路径读取和写入值。 你通过在浏览器中直接运行的动手代码来练习 Swift Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Swift Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Swift Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「可写键路径与引用键路径」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Swift Academy 课中编写并运行代码吗?
能。每节 Swift Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。