【问题标题】:Why do I have to add != to make Equatable works?为什么我必须添加 != 才能使 Equatable 工作?
【发布时间】:2017-08-09 02:54:24
【问题描述】:

为什么我必须添加 != 才能使比较正确?

import UIKit

class Person: NSObject {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

extension Person {
    static func ==(lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name && lhs.age == rhs.age
    }
    static func !=(lhs: Person, rhs: Person) -> Bool {
        return !(lhs == rhs)
    }
}

let first = Person(name: "John", age: 26) 
let second = Person(name: "John", age: 26)

/**
 * return false (which is correct) when we implement != function. But,
 * it will return true if we don't implement the != function.
 */
first != second 

更新: 所以我明白了为什么我必须添加!= 函数才能使其工作。这是因为该类继承了NSObject,它在后台使用isEqual 方法。但是为什么添加!= 函数可以让它工作呢?这里有什么解释吗?

【问题讨论】:

  • @Alexander 抱歉,这里的类代码太复杂了。但答案可能很简单,因为我读到这只是新版本 Swift 的新要求,您必须实现 != 才能使其工作。
  • 这个假设是错误的。提供了一个默认的!= 实现,它调用!(a == b)。如果您甚至不给我们重新创建问题的方法,我不确定您正在寻找什么样的答案
  • 虽然我没有答案,但我很欣赏更新后的问题。你有我的赞成票。
  • 比较 stackoverflow.com/a/42286148/2976878 的第 2 点。简而言之,子类不能直接重新实现超类的协议一致性。虽然您的== 实现可以静态 分派到,但它不能动态 分派到(例如从调用@987654333 的标准库的!= 运算符实现中) @ 并否定结果)。正如其他人已经说过的那样,您想覆盖isEqual hash!)

标签: swift


【解决方案1】:

NSObject 符合 Equatable 但使用自己的 isEqual 方法,并且就 isEqual 而言,两个实例不相等。 NSObject 仅当您的 != 形式已实现时才调用 ==,其中包含 ==

如果您删除 NSObject(并添加 Equatable),== 的实现将按预期工作。

NSObject 的推荐方法是使用自定义实现覆盖 isEqual 并省略 ==(和 !=)。

【讨论】:

    【解决方案2】:

    抱歉,这不是您问题的直接答案。

    正如 Alexander 所评论的,Swift 标准库具有 != 的默认实现:

    Equatable.swift

      @_transparent
      public static func != (lhs: Self, rhs: Self) -> Bool {
        return !(lhs == rhs)
      }
    

    我无法很好地解释这种行为,但是上面默认实现中的== 运算符已解决为NSObject 的默认== 运算符,因为NSObject(及其后代)已经是Equatable并有一个== 运算符以符合Equatable。因此,即使显式表示与您的 != 定义完全相同,== 运算符也会被解析为不同的实现。


    定义您自己与 NSObject-descendant 类相等的一般准则:

    使==isEqual(_:) 一致

    您可以将类的实例存储在NSArrayNSDictionary 中(在许多情况下是隐式的)。在他们的方法中,isEqual(_:) 在需要相等检查时使用,而不是 == 运算符。

    所以,只定义== 运算符而不给isEqual(_:) 提供一致的覆盖,这样的方法会产生意想不到的结果。

    为了使==isEqual(_:)一致,

    只覆盖isEqual(_:),不要明确定义==!=

    == 的默认实现 NSObject(以及 !=)使用 isEqual(_:)

    class Person: NSObject {
        var name: String
        var age: Int
    
        init(name: String, age: Int) {
            self.name = name
            self.age = age
        }
    
        override func isEqual(_ object: Any?) -> Bool {
            if let other = object as? Person {
                return self.name == other.name && self.age == other.age
            }
            return false
        }
    }
    

    (请参阅底部的另一件事。)


    添加

    在非NSObject 类上可以找到类似的行为。

    class BaseClass {
        var a: Int
    
        init(a: Int) {
            self.a = a
        }
    }
    extension BaseClass: Equatable {
        static func == (lhs: BaseClass, rhs: BaseClass) -> Bool {
            print("`==` of BaseClass")
            return lhs.a == rhs.a
        }
    }
    let b1 = BaseClass(a: 0)
    let b2 = BaseClass(a: 0)
    print(b1 != b2) //->`==` of BaseClass, false ### as expected
    
    class DerivedClass: BaseClass {
        var b: Int
    
        init(a: Int, b: Int) {
            self.b = b
            super.init(a: a)
        }
    }
    extension DerivedClass {
        static func == (lhs: DerivedClass, rhs: DerivedClass) -> Bool {
            print("`==` of DerivedClass")
            return lhs.a == rhs.a && lhs.b == rhs.b
        }
    }
    let d1 = DerivedClass(a: 0, b: 1)
    let d2 = DerivedClass(a: 0, b: 2)
    print(d1 != d2) //->`==` of BaseClass, false ### `==` of DerivedClass and true expected
    

    在为已经 Equatable 类覆盖 == 时,我们似乎需要格外小心。


    还有一件事

    (感谢哈米什。)

    您知道在创建符合Hashable 的类型时,您需要一致地实现==hashValueNSObject被声明为Hashable,其hashValue需要与hash保持一致。因此,当您在 NSObject-descendent 中覆盖 isEqual(_:) 时,您还应该覆盖 hash 与覆盖的 isEqual(_:) 一致。

    所以,你的Person 类应该是这样的:

    class Person: NSObject {
        var name: String
        var age: Int
    
        init(name: String, age: Int) {
            self.name = name
            self.age = age
        }
    
        override func isEqual(_ object: Any?) -> Bool {
            if let other = object as? Person {
                return self.name == other.name && self.age == other.age
            }
            return false
        }
    
        override var hash: Int {
            //### This is just an example, but not too bad in practical use cases.
            return name.hashValue ^ age.hashValue
        }
    }
    

    【讨论】:

    • 是的,似乎除了覆​​盖 isEqual 之外别无他法。因为如果我将== 用于 swift 类,将isEqual 用于NSObject,那么我忘记它是一个 NSObject、写== 并破坏一切只是时间问题。通过覆盖isEqual,我认为它可以使代码更加一致,所以我们可以在任何地方使用==。顺便说一句,你在哪里找到!= 方法的实现?
    • @EdwardAnthony,对不起,我忘记包含它的链接。即将更新。
    • 非常感谢。
    • 注意如果你覆盖isEqual,你真的应该覆盖hash,以确保如果两个对象确定相等,它们具有相同的哈希值。您看到的行为是由于子类没有直接重新实现超类对协议的一致性(比较stackoverflow.com/a/42286148/2976878 的第 2 点:))
    • @Hamish,我应该提到它! (坦率地说,当我写下我的答案时,我几乎忘记了它。)非常感谢!
    【解决方案3】:

    你做错了。你不应该实现==!=。 NSObject 子类自动将== 实现为isEqual:。你正在破坏它。你应该实现isEqual:,仅此而已。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-19
      • 2014-07-06
      • 2021-12-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多