首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Swift - KeyPath扩展-强制遵守协议

Swift - KeyPath扩展-强制遵守协议
EN

Stack Overflow用户
提问于 2020-08-27 07:23:27
回答 2查看 290关注 0票数 2

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

因此,我的想法是,KeyPath可以根据开发人员添加的协议扩展来提供此信息。然后,我想设计一个带有初始化器/函数的应用程序接口,它接受符合该协议的KeyPath (添加了一个计算属性)。

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

代码语言:javascript
复制
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,并且必须符合某个协议。

例如,下面的代码可能会导致编译错误,但根据我的理解应该是有效的(但我可能遗漏了一个关键细节;)

代码语言:javascript
复制
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"
    }
}

任何提示都是非常感谢的!

EN

回答 2

Stack Overflow用户

发布于 2020-08-27 09:43:34

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

代码语言:javascript
复制
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只在某些时候才可用。

如果我这样重写初始化器...

代码语言:javascript
复制
init<T, KeyPathType: KeyPath<Model, T> & KeyPathPropertyNameProviding>(property: KeyPathType) {
    self.propertyName = property.propertyName
}

这会以某种方式使错误消失并生成警告:

KeyPathType冗余一致性约束‘

’:'KeyPathPropertyNameProviding'

票数 1
EN

Stack Overflow用户

发布于 2020-08-27 12:29:24

键路径是hashable的,所以我推荐使用字典。如果您能够使用CodingKey类型,那么将其与强类型放在一起尤其容易。

代码语言:javascript
复制
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]!
  }
}

然后使用括号,而不是您演示的强制转换。到目前为止还不需要协议,…

代码语言:javascript
复制
(\Person.name).label // "name"
(\Person.age).label // "age"

有一天,由于内置的支持,这一切可能都会变得更干净。https://forums.swift.org/t/keypaths-and-codable/13945

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63607042

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档