理想情况下,我希望获得由KeyPath引用的属性的名称。但在Swift中,这似乎不可能开箱即用。
因此,我的想法是,KeyPath可以根据开发人员添加的协议扩展来提供此信息。然后,我想设计一个带有初始化器/函数的应用程序接口,它接受符合该协议的KeyPath (添加了一个计算属性)。
到目前为止,我只能定义协议和协议的条件一致性。下面的代码编译得很好。
protocol KeyPathPropertyNameProviding {
var propertyName: String {get}
}
struct User {
var name: String
var age: Int
}
struct Person {
var name: String
var age: Int
}
extension KeyPath: KeyPathPropertyNameProviding where Root == Person {
var propertyName: String {
switch self {
case \Person.name: return "name"
case \Person.age: return "age"
default: return ""
}
}
}
struct PropertyWrapper<Model> {
var propertyName: String = ""
init<T>(property: KeyPath<Model, T>) {
if let property = property as? KeyPathPropertyNameProviding {
self.propertyName = property.propertyName
}
}
}
let userAge = \User.age as? KeyPathPropertyNameProviding
print(userAge?.propertyName) // "nil"
let personAge = \Person.age as? KeyPathPropertyNameProviding
print(personAge?.propertyName) // "age"
let wrapper = PropertyWrapper<Person>(property: \.age)
print(wrapper.propertyName) // "age"但是我不能限制接口,使得初始化参数property必须是一个KeyPath,并且必须符合某个协议。
例如,下面的代码可能会导致编译错误,但根据我的理解应该是有效的(但我可能遗漏了一个关键细节;)
struct PropertyWrapper<Model> {
var propertyName: String = ""
init<T>(property: KeyPath<Model, T> & KeyPathPropertyNameProviding) {
self.propertyName = property.propertyName // compilation error "Property 'propertyName' requires the types 'Model' and 'Person' be equivalent"
}
}任何提示都是非常感谢的!
发布于 2020-08-27 09:43:34
您误解了条件一致性。您似乎想在将来这样做:
extension KeyPath: KeyPathPropertyNameProviding where Root == Person {
var propertyName: String {
switch self {
case \Person.name: return "name"
case \Person.age: return "age"
default: return ""
}
}
}
extension KeyPath: KeyPathPropertyNameProviding where Root == User {
var propertyName: String {
...
}
}
extension KeyPath: KeyPathPropertyNameProviding where Root == AnotherType {
var propertyName: String {
...
}
}但您不能。您正在尝试指定多个条件以符合同一协议。有关Swift中未包含此功能的详细信息,请参阅here。
不知何故,编译器的一部分认为符合KeyPathPropertyNameProviding是没有条件的,所以KeyPath<Model, T> & KeyPathPropertyNameProviding实际上与KeyPath<Model, T>是一样的,因为不知何故,就编译器而言,KeyPath<Model, T>已经“符合”了KeyPathPropertyNameProviding,只是属性propertyName只在某些时候才可用。
如果我这样重写初始化器...
init<T, KeyPathType: KeyPath<Model, T> & KeyPathPropertyNameProviding>(property: KeyPathType) {
self.propertyName = property.propertyName
}这会以某种方式使错误消失并生成警告:
KeyPathType冗余一致性约束‘
’:'KeyPathPropertyNameProviding'
发布于 2020-08-27 12:29:24
键路径是hashable的,所以我推荐使用字典。如果您能够使用CodingKey类型,那么将其与强类型放在一起尤其容易。
struct Person: Codable {
var name: String
var age: Int
enum CodingKey: Swift.CodingKey {
case name
case age
}
}
extension PartialKeyPath where Root == Person {
var label: String {
[ \Root.name: Root.CodingKey.name,
\Root.age: .age
].mapValues(\.stringValue)[self]!
}
}然后使用括号,而不是您演示的强制转换。到目前为止还不需要协议,…
(\Person.name).label // "name"
(\Person.age).label // "age"有一天,由于内置的支持,这一切可能都会变得更干净。https://forums.swift.org/t/keypaths-and-codable/13945
https://stackoverflow.com/questions/63607042
复制相似问题