0Pricing
Swift Academy · 课时

可写键路径与引用键路径

通过键路径读取和写入值。

可写键路径与引用键路径 是 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. KeyPath 基础
  2. 在集合中使用 KeyPaths
  3. 可写键路径与引用键路径
  4. @dynamicMemberLookup
← 返回 Swift Academy