SE-0227 中描述的 KeyPath 是 Swift 5 加入的一个特性。它可以让我们把一个对象的属性,当成另外一个独立的值使用。这个值可以作为参数,可以用在表达式里。这也就意味着,我们甚至可以在不知道一个属性所属的具体类型的前提下,就对其进行赋值。在 Swift 5.2 里,SE-0249 还让 KeyPath 自身可以作为一个函数,传递给高阶函数作为谓词使用。

那么为什么现在要提到这个特性呢?用一句话表达就是:我们期望用一个泛型函数对从 Core Data 读取出来的结果集合按照其元素的某种属性进行排序。例如下面这样:

private func loadRecentUpdates() -> [Episode] {
  return load(by: \.updatedAt, criteria: >)
}

它可以按照更新时间对从 Core Data 取出来的最近更新视频记录进行排序。而 load 的实现,就会用到 KeyPath。因此,如果你还不熟悉 KeyPath,这一节,我们用最简单的例子讲下这三种不同的类型:KeyPath / WritableKeyPath / ReferenceWritableKeyPath。如果你已经了解它们,就可以直接跳到下一节了。

KeyPath

首先,我们从最简单的 KeyPath 类型说起。现在,假设有一个表示视频的值类型:

struct Episode {
  let id: Int
  let title: String
}

let first = Episode(id: 1, title: "Episode1")

现在,为了访问 first.id,除了直接用 . 之外,我们还可以这样:

let id = first[keyPath: \Episode.id]
print(type(of: \Episode.id)) // KeyPath<Episode, Int>

first[keyPath: \Episode.id]first.id 的结果,是相同的。这里,\Episode.id 就是 id 这个属性的 KeyPath,当然,如果编译器可以根据上下文推断出 id 所属的类型,也可以进一步简写成 \.id。它的类型是 KeyPath<Episode, Int>,第一个泛型参数表示要引用的类型,第二个泛型参数表示其中要指定的属性的类型。

看到这,如果你有一些 Objective-C 的编程经验一定会想,尽管 id 是一个常量无法直接赋值,但我能用 KeyPath 间接修改它么:

first[keyPath: \Episode.id] = 2

答案是不行。编译器会提示你:Cannot assign through subscript: key path is read-only。即便我们把 Episode 改成这样:

struct Episode {
  var id: Int
  /// ...
}

编译器还是会给出同样的提示,因为 first 还是一个常量。这和 Swift 中值类型的可修改机制是完全一样的。

WritableKeyPath

因此,为了通过 keypath 修改 id,我们还需要让 first 从常量变成变量:

var first = Episode(id: 1, title: "Episode1")

此时 \Episode.id 的类型就变成了 WritableKeyPath<Episode, Int>

ReferenceWritableKeyPath

接下来,我们把这个例子改成这样:

class Episode {
  var id: Int
  let title: String

  init(id: Int, title: String) {
    self.id = id
    self.title = title
  }
}

let first = Episode(id: 1, title: "Episode1")

此时,尽管 first 是一个常量。我们还是可以用 first.id 的形式修改 id 属性。因此,按道理说,KeyPath 同样应该可以:

first[keyPath: \Episode.id] = 2

只是这次,\Episode.id 的类型,就变成了 ReferenceWritableKeyPath<Episode, Int>。那么,如果把 id 再改回常量呢?这样,即便 Episode 是一个引用类型,first.id 也不可修改了。此时 Episode.id 就会变回 KeyPath<Episode, Int>

What's next?

看到这,你可能会觉得,这有什么用呢?除了访问属性更麻烦了之外,似乎没什么用处。的确,如果只是单纯用 KeyPath 访问对象属性,的确没什么大用。KeyPath 真正有用的地方在于,它可以让我们把类对象属性的引用和具体的类对象剥离开。下一节,我们就基于这个特性,为 Sequence 编写一个有用的扩展。简化从 Core Data 中读取记录时,对结果的搜索。

所有订阅均支持 12 期免息分期

¥ 59

按月订阅

一个月,观看并下载所有视频内容。初来泊学,这可能是个最好的开始。

开始订阅

¥ 512

按年订阅

一年的时间,让我们一起疯狂地狩猎知识吧。比按月订阅优惠 28%

开始订阅

¥ 1280

泊学终身会员

永久观看和下载所有泊学网站视频,并赠送 100 元商店优惠券。

我要加入
如需帮助,欢迎通过以下方式联系我们