【问题标题】:Swift - KeyPath extension - enforce conformance to protocolSwift - KeyPath 扩展 - 强制遵守协议
【发布时间】:2020-08-26 23:23:27
【问题描述】:

理想情况下,我想获取 KeyPath 引用的属性的名称。但这在 Swift 中似乎不可能开箱即用。

所以我的想法是 KeyPath 可以根据开发人员添加的协议扩展提供此信息。然后我想设计一个带有初始化器/函数的 API,它接受符合该协议的 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"

但我无法限制 API,因此初始化参数 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"
    }
}

非常感谢任何提示!

【问题讨论】:

    标签: swift


    【解决方案1】:

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

    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 {
            ...
        }
    }
    

    但你不能。您正在尝试指定多个条件以符合同一协议。请参阅here 了解更多关于为什么这不在 Swift 中的信息。

    不知何故,编译器的一部分认为与KeyPathPropertyNameProviding 的一致性不是有条件的,因此KeyPath&lt;Model, T&gt; &amp; KeyPathPropertyNameProviding 实际上与KeyPath&lt;Model, T&gt; 相同,因为到目前为止KeyPath&lt;Model, T&gt; 已经“符合”KeyPathPropertyNameProviding就编译器而言,只是属性propertyName 仅在有时可用。

    如果我这样重写初始化程序...

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

    不知何故使错误消失并产生警告:

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

    【讨论】:

      【解决方案2】:

      键路径是可散列的,所以我推荐使用字典。如果您能够使用 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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多