Swift-密钥路径扩展-强制遵守协议

Swift-密钥路径扩展-强制遵守协议,swift,Swift,理想情况下,我希望获得键路径引用的属性的名称。但在Swift中,这似乎不可能开箱即用 所以我的想法是,KeyPath可以根据开发人员添加的协议扩展提供这些信息。然后,我想设计一个带有初始值设定项/函数的API,它接受符合该协议的密钥路径(添加计算属性) 到目前为止,我只能定义协议和协议的条件一致性。下面的代码编译得很好 protocol KeyPathPropertyNameProviding { var propertyName: String {get} } struct User

理想情况下,我希望获得键路径引用的属性的名称。但在Swift中,这似乎不可能开箱即用

所以我的想法是,KeyPath可以根据开发人员添加的协议扩展提供这些信息。然后,我想设计一个带有初始值设定项/函数的API,它接受符合该协议的密钥路径(添加计算属性)

到目前为止,我只能定义协议和协议的条件一致性。下面的代码编译得很好

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"

任何提示都非常感谢

您误解了条件一致性。您似乎希望在将来这样做:

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中没有此选项的更多信息,请参阅

不知何故,编译器的一部分认为与
KeyPath-propertyNameProvising
的一致性不是有条件的,因此
KeyPath&KeyPath-propertyNameProvising
实际上与
KeyPath
相同,因为
KeyPath
已经“一致”对于
KeyPathPropertyName
,就编译器而言,只是属性
propertyName
有时才可用

如果我这样重写初始化器

init<T, KeyPathType: KeyPath<Model, T> & KeyPathPropertyNameProviding>(property: KeyPathType) {
    self.propertyName = property.propertyName
}
init(属性:KeyPathType){
self.propertyName=property.propertyName
}
这会以某种方式使错误消失并产生警告:

冗余一致性约束“KeyPathType”:“KeyPathPropertyNameProvising”


密钥路径是可散列的,因此我建议使用字典。如果您能够使用
CodingKey
类型,那么将其与强类型组合起来就特别容易

struct Person:Codable{
变量名称:String
变量年龄:Int
枚举编码键:Swift.CodingKey{
案例名称
病例年龄
}
}
扩展PartialKeyPath,其中Root==个人{
变量标签:字符串{
[\Root.name:Root.CodingKey.name,
\Root.age:.age
].mapValues(\.stringValue)[self]!
}
}
然后使用括号,而不是您演示的类型转换。目前还不需要协议

(\Person.name).label/“name”
(\Person.age).标签//“age”
有一天,由于内置的支持,这一切可能会更干净

init<T, KeyPathType: KeyPath<Model, T> & KeyPathPropertyNameProviding>(property: KeyPathType) {
    self.propertyName = property.propertyName
}