Swift 5 存储和传递 KeyPaths

Swift 5 storing and passing KeyPaths

假设我有以下 class:

class User: NSObject {
  var name = "Fred"
  var age = 24
  var email = "fred@freddy.com"
  var married = false
}

我希望能够编写一个通用函数,该函数接受已知 class 类型的 KeyPath 列表,读取值并打印到屏幕。问题是,我无法编译以下代码,因为 KeyPathValue 的类型未知,并且每次都会不同。我需要做些什么才能使这项工作正常进行?

考虑以下几点:

struct KeyPathProperties<T> {
  var name: String
  var relatedKeyPaths: [KeyPath<T, Any>]
}

extension KeyPath where Root == User {
  var properties: KeyPathProperties<Root> {
    switch self {
      case \Root.name:
        return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])
      default:
        fatalError("Unknown key path")
    }
  }
}

这一行编译失败:

return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])

出现此错误:

Cannot convert value of type 'KeyPath<User, Int>' to expected element type 'KeyPath<User, Any>'

这是我希望能够做到的,例如:

let myUser = User()

var keyPathProps = KeyPathProperties(name: "name", relatedKeyPaths: [\User.age, \User.email])

for keyPath in props.relatedKeyPaths {
  print("Value: \(myUser[keyPath: keyPath])")
}

以上当然不会编译。本质上,我想在运行时将 keyPaths 存储在数组中,因此我通常可以在某个时间点从 User 中获取值。我需要知道我是否可以以编译器可以在运行时安全正确地确定 keyPath 值的类型的某种方式重写上面的内容。

这是一个更复杂的架构问题的概念用例,我正试图用更少的代码来解决。

更多信息:

在运行时我希望跟踪被修改的属性 - 这些属性保存在每个对象/实例的 modifiedProps 数组中。在运行时的某个时候,我希望能够枚举这个 KeyPaths 数组并像这样打印它们的值:

for modifiedKeyPath in self.modifiedProps { 
  print ("\(self[keyPath: modifiedKeyPath])" 
}

简而言之 - 我需要能够在 KeyPathProperties 中捕获 KeyPath 的通用类型。我该如何实现?

旁注:我已经可以通过使用 Swift 3 种基于字符串的 KeyPath 样式轻松实现此目的(通过将 @objc 添加到 class 属性)。我可以将一组 keyPaths 存储为字符串,然后再执行:

let someKeyPath = #keyPath(User.email)
...

myUser.value(forKeyPath: someKeyPath)

我只是不能用 Swift 4 个 KeyPaths 一般地做到这一点。

错误告诉你你的误解是什么:

Cannot convert value of type 'KeyPath<User, Int>' 
    to expected element type 'KeyPath<User, Any>'

你似乎认为你可以在需要 KeyPath<User, Any> 的地方使用 KeyPath<User, Int>,表面上是因为 Int 是 Any。但事实并非如此。这些是泛型类型,泛型类型不是协变的——也就是说,没有基于它们的参数化类型的泛型替换原则。这两种类型实际上是无关的。

如果您需要一个键路径数组,而不管它们的参数化类型如何,您将需要一个 PartialKeyPath 或 AnyKeyPath 数组。似乎在您的用例中,根对象始终是相同的,因此您可能需要 PartialKeyPath。