Swift Academy · 课时

在集合中使用 KeyPaths

根据键路径对集合排序和映射。

第 2 / 4 课13 个步骤

在集合中使用 KeyPaths 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 4 节课。

键路径在集合中大放异彩

集合操作(如 map、sorted 和 filter)通常只需读取每个元素的一个属性。键路径让您无需编写闭包即可表达这一意图。

Swift 接受键路径出现在任何需要一个返回值的单参数函数的位置,因为键路径可以用作从 Root 到 Value 的函数。

struct Person {
    let name: String
    let age: Int
}

let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let names = people.map(\.name)
print(names)

使用键路径简写的 map

传递 map(\.property) 等同于编写一个返回该属性的闭包,但写法更短,也更清晰。开头的反斜杠和点号让编译器能够推断元素类型。

比较下面两种形式;它们会产生相同的结果。

struct Person {
    let name: String
    let age: Int
}

let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let closureForm = people.map { $0.age }
let keyPathForm = people.map(\.age)
print(closureForm)
print(keyPathForm)

使用键路径比较的 sorted(by:)

要按某个属性排序,您仍需编写一个小型比较闭包,但可以在其中通过键路径读取该属性,使意图更清晰。

这里我们通过比较每个人的 age 值,按从年轻到年长的顺序排列人员。

struct Person {
    let name: String
    let age: Int
}

let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let path = \Person.age
let sorted = people.sorted { $0[keyPath: path] < $1[keyPath: path] }
print(sorted.map(\.name))

可复用的排序辅助函数

您可以编写一个通用辅助函数,按照任意可比较属性对任意数组进行排序,并将该属性作为键路径参数传入。这样,在整个代码库中按字段排序都可以写成一行代码。

约束 T: Comparable 确保该属性可以进行排序。

func sorted<E, T: Comparable>(_ array: [E], by path: KeyPath<E, T>) -> [E] {
    array.sorted { $0[keyPath: path] < $1[keyPath: path] }
}

struct Person { let name: String; let age: Int }
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
print(sorted(people, by: \.age).map(\.name))

通过键路径进行筛选

对于 filter,您需要一个返回 Bool 的谓词,因此,只有当属性本身已经是 Bool 时,单独的属性键路径才能直接使用。

这里每个任务都有一个 isDone 标志,我们使用键路径简写根据它进行筛选。

struct Task {
    let title: String
    let isDone: Bool
}

let tasks = [Task(title: "A", isDone: true), Task(title: "B", isDone: false)]
let finished = tasks.filter(\.isDone)
print(finished.map(\.title))

筛选非 Bool 属性

按非 Bool 属性筛选时,请在闭包中通过键路径读取该属性并进行比较。通用辅助函数可以为任何可相等比较的属性封装这一模式。

这里我们只保留 city 与目标相匹配的人员。

struct Person { let name: String; let city: String }

func filter<E, T: Equatable>(_ array: [E], _ path: KeyPath<E, T>, equals value: T) -> [E] {
    array.filter { $0[keyPath: path] == value }
}

let people = [Person(name: "Ana", city: "Oslo"), Person(name: "Ben", city: "Rome")]
print(filter(people, \.city, equals: "Oslo").map(\.name))

通过键路径使用 max 和 min

max(by:) 和 min(by:) 方法与 sorted 一样,都接受比较闭包。通过键路径读取属性,即可找到具有最大值或最小值的元素。

这里我们找出年龄最大的人。

struct Person { let name: String; let age: Int }

let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25), Person(name: "Cy", age: 41)]
let path = \Person.age
if let oldest = people.max(by: { $0[keyPath: path] < $1[keyPath: path] }) {
    print(oldest.name)
}

使用 reduce 对属性求和

您可以将 map(\.property) 与 reduce 结合起来,计算数值字段的总和。先将每个元素转换为所需的数字,再把它们相加。

这里我们在一个流程中对所有年龄求和。

struct Person { let name: String; let age: Int }

let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let totalAge = people.map(\.age).reduce(0, +)
print(totalAge)

按键路径分组

Dictionary(grouping:by:) 会根据闭包的结果将元素分桶。通过键路径读取属性,即可按该字段进行分组。

这里我们按 city 将人员分组,得到一个由数组组成的字典。

struct Person { let name: String; let city: String }

let people = [
    Person(name: "Ana", city: "Oslo"),
    Person(name: "Ben", city: "Rome"),
    Person(name: "Cy", city: "Oslo")
]
let path = \Person.city
let byCity = Dictionary(grouping: people, by: { $0[keyPath: path] })
print(byCity["Oslo"]?.map(\.name) ?? [])

按多个字段排序

要先按一个字段排序,并在出现相同值时使用另一个字段打破平局,请先比较主要键路径;当主要值相等时,再回退到次要键路径。

这里我们先按 age 排序,年龄相同时再按姓名排序。

struct Person { let name: String; let age: Int }

let people = [
    Person(name: "Zoe", age: 30),
    Person(name: "Ana", age: 30),
    Person(name: "Ben", age: 25)
]
let sorted = people.sorted {
    if $0[keyPath: \Person.age] != $1[keyPath: \Person.age] {
        return $0.age < $1.age
    }
    return $0.name < $1.name
}
print(sorted.map(\.name))

为什么这里键路径优于闭包

在集合调用中使用键路径可以减少样板代码,避免闭包主体中出现拼写错误,并清楚表达您的意图:您要投影出单个字段。

对于复杂的转换,闭包仍然是合适的工具;但对于简单的属性读取,\.property 是最清晰的选择。

struct Product { let name: String; let price: Double }

let products = [Product(name: "Pen", price: 2), Product(name: "Pad", price: 5)]
let names = products.map(\.name)
let prices = products.map(\.price)
print(names)
print(prices)

快速检查:map 中的键路径

请选择从每个元素中提取一个属性的简洁键路径形式。

回顾:集合中的键路径

您已经了解了键路径如何让集合代码更加简洁:

  • map(\.property) 无需闭包即可投影出单个字段。
  • filter(\.boolProperty) 可直接用于 Bool 字段;对于其他字段,请使用带有 [keyPath:] 的闭包。
  • 排序、max 和 min 会在比较闭包中通过键路径读取属性。
  • 接收 KeyPath 的通用辅助函数,可以将按字段排序和筛选变成可复用的单行代码。

接下来您将学习可写键路径,它们不仅能读取值,还能设置值。

struct Person { let name: String; let age: Int }

let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let youngestFirst = people.sorted { $0[keyPath: \Person.age] < $1[keyPath: \Person.age] }
print(youngestFirst.map(\.name))
免费开始

用 AI 导师学习 Swift — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
122
课程
409

常见问题解答

「在集合中使用 KeyPaths」课时是免费的吗?

是的 — 「在集合中使用 KeyPaths」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Swift Academy 课程的其余内容,请升级到 CoddyKit PRO。 Swift Academy 课程共包含 4 节课。

「在集合中使用 KeyPaths」这节课中我会学到什么?

根据键路径对集合排序和映射。 你通过在浏览器中直接运行的动手代码来练习 Swift Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Swift Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Swift Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「在集合中使用 KeyPaths」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Swift Academy 课中编写并运行代码吗?

能。每节 Swift Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

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