【问题标题】:Is there way to define compare (`==`) function automatically for `struct` in Swift?有没有办法在Swift中为`struct`自动定义比较(`==`)函数?
【发布时间】:2016-01-26 21:03:15
【问题描述】:

假设我们在 Swift 中有一个相当大的 struct

struct SuperStruct {
    var field1: Int = 0
    var field2: String = ""
    // lots of lines...
    var field512: Float = 0.0
}

..然后我们需要实现Equatable协议:

extension SuperStruct: Equatable {
}

func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {
    return
        lhs.field1 == rhs.field1 &&
        lhs.field2 == rhs.field2 &&
        // lots of lines...
        lhs.field512 == rhs.field512
}

...我们需要编写很多行愚蠢的代码。 有没有办法“要求”编译器为我们“做”它?

【问题讨论】:

  • 你应该看看协议扩展。 WWDC 2015 会议 408。devstreaming.apple.com/videos/wwdc/2015/408509vyudbqvts/408/…
  • 编写一个小助手来解析一段代码并生成所需的方法有多难?在此基础上添加 bean 的版本以跟踪可能需要更新的代码,你很好。
  • Leo Dabus,谢谢!我知道协议扩展是如何工作的,但我不知道如何在我的问题上使用它。
  • A-Live,是的,我可以做这样的事情,但我正在寻找编译器原生功能。我只是相信它应该存在。

标签: swift swift-structs


【解决方案1】:

以下答案显示了一个可能的解决方案;可能不是推荐的(但可能对这个问题的未来读者感兴趣)。


如果您有大量属性都属于数量有限的不同类型,您可以使用结构实例的Mirror 并迭代结构的属性;每次尝试转换为你知道你的属性的不同类型。

在观看了以下 WWDC 2015 会议(感谢 Leo Dabus!)之后,我编辑了之前的答案(我认为更简洁的内容):

我也会将最初的答案留在这个答案的底部,因为它显示了一种替代的、较少面向协议的方法来利用这个Mirror 解决方案。

Mirror & 面向协议的解决方案:

/* Let a heterogeneous protocol act as "pseudo-generic" type
   for the different (property) types in 'SuperStruct'         */
protocol MyGenericType {
    func isEqualTo(other: MyGenericType) -> Bool
}
extension MyGenericType where Self : Equatable {
    func isEqualTo(other: MyGenericType) -> Bool {
        if let o = other as? Self { return self == o }
        return false
    }
}

/* Extend types that appear in 'SuperStruct' to MyGenericType  */
extension Int : MyGenericType {}
extension String : MyGenericType {}
extension Float : MyGenericType {}
    // ...

/* Finally, 'SuperStruct' conformance to Equatable */
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {

    let mLhs = Mirror(reflecting: lhs).children.filter { $0.label != nil }
    let mRhs = Mirror(reflecting: rhs).children.filter { $0.label != nil }

    for i in 0..<mLhs.count {
        guard let valLhs = mLhs[i].value as? MyGenericType, valRhs = mRhs[i].value as? MyGenericType else {
            print("Invalid: Properties 'lhs.\(mLhs[i].label!)' and/or 'rhs.\(mRhs[i].label!)' are not of 'MyGenericType' types.")
            return false
        }
        if !valLhs.isEqualTo(valRhs) {
            return false
        }
    }
    return true
}

示例用法:

/* Example */
var a = SuperStruct()
var b = SuperStruct()
a == b // true
a.field1 = 2
a == b // false
b.field1 = 2
b.field2 = "Foo"
a.field2 = "Foo"
a == b // true

以前的Mirror 解决方案:

/* 'SuperStruct' conformance to Equatable */
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {

    let mLhs = Mirror(reflecting: lhs).children.filter { $0.label != nil }
    let mRhs = Mirror(reflecting: rhs).children.filter { $0.label != nil }

    for i in 0..<mLhs.count {
        switch mLhs[i].value {
        case let valLhs as Int:
            guard let valRhs = mRhs[i].value as? Int where valRhs == valLhs else {
                return false
            }
        case let valLhs as String:
            guard let valRhs = mRhs[i].value as? String where valRhs == valLhs else {
                return false
            }
        case let valLhs as Float:
            guard let valRhs = mRhs[i].value as? Float where valRhs == valLhs else {
                return false
            }
            /* ... extend with one case for each type
            that appear in 'SuperStruct'  */
        case _ : return false
        }
    }
    return true
}

示例用法:

/* Example */
var a = SuperStruct()
var b = SuperStruct()
a == b // true
a.field1 = 2
a == b // false
b.field1 = 2
b.field2 = "Foo"
a.field2 = "Foo"
a == b // true

【讨论】:

  • 哇,不错的 hack。 +1。可怕,并且可能证明一个人可能不应该尽量避免更直接地编写Equatable 一致性。
  • 太棒了!可能它不是生产解决方案(我相信它应该很慢)但我绝对可以在开发中使用它,而 SuperStruct 中的一组属性不稳定。
  • 如何让 Array 符合 MyGenericType?
【解决方案2】:

在 Swift 4.1 中,如果所有类型的成员都是 Equatable/Hashable,则 Equatable/Hashable 类型现在综合符合 Equatable/Hashable

SE-0185

综合 Equatable 和 Hashable 一致性

开发人员必须编写大量样板代码来支持复杂类型的等价性和哈希性。该提案为编译器提供了一种方法,可以在已知可能生成正确实现的场景子集中自动综合符合 Equatable 和 Hashable 以减少此样板。

https://github.com/apple/swift-evolution/blob/master/proposals/0185-synthesize-equatable-hashable.md

【讨论】:

    【解决方案3】:

    不,它没有。至少不是以任何不是过于复杂并且基于运行时内省的使用(滥用?)的方式。请参阅 dfri's answer 了解技术上可行的方法,但这方式比编写一个直接比较所有字段的 == 实现要复杂。

    至于你对 Swift 中“应该”提供什么的意见,如果你分享它们with AppleSwift open source community,你更有可能看到一些效果。

    【讨论】:

      【解决方案4】:

      您可以使结构可编码并比较 JSON 编码的数据。效率不高,但可能对某些应用程序有用(例如单元测试)。

      struct SuperStruct: Encodable {
          var field1: Int = 0 
          // ....
          var field512: Float = 0.0
      }
      
      let s1 = SuperStruct()
      let s2 = SuperStruct()
      
      let encoder = JSONEncoder()
      let data1 = try! encoder.encode(s1)
      let data2 = try! encoder.encode(s2)
      let result = (data1 == data2)
      

      如果你喜欢这个,你可以把它整理成Encodable的协议扩展。

      【讨论】:

        猜你喜欢
        • 2020-03-05
        • 1970-01-01
        • 2014-07-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多