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
列表,读取值并打印到屏幕。问题是,我无法编译以下代码,因为 KeyPath
的 Value
的类型未知,并且每次都会不同。我需要做些什么才能使这项工作正常进行?
考虑以下几点:
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。
假设我有以下 class:
class User: NSObject {
var name = "Fred"
var age = 24
var email = "fred@freddy.com"
var married = false
}
我希望能够编写一个通用函数,该函数接受已知 class 类型的 KeyPath
列表,读取值并打印到屏幕。问题是,我无法编译以下代码,因为 KeyPath
的 Value
的类型未知,并且每次都会不同。我需要做些什么才能使这项工作正常进行?
考虑以下几点:
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。