Swift,如何实现基于对象引用的Hashable协议?

jm81lzqq  于 2023-03-07  发布在  Swift
关注(0)|答案(5)|浏览(127)

在Java之后我开始学习Swift。在Java中我可以使用任何对象作为HashSet的键,因为它有基于对象标识符的默认hashCodeequals。如何在Swift中实现同样的行为?

k2arahey

k2arahey1#

如果您使用的是类而不是结构体,则可以使用ObjectIdentifier结构体。请注意,您还必须为类定义==,以便符合EquatableHashable需要它)。它看起来如下所示:

class MyClass: Hashable { }

func ==(lhs: MyClass, rhs: MyClass) -> Bool {
    return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}

class MyClass: Hashable {
    var hashValue: Int {
        return ObjectIdentifier(self).hashValue
    }
}
7fyelxc5

7fyelxc52#

在Swift中,类型必须符合HashableEquatable,才能在DictionarySet这样的数据结构中使用。然而,您可以通过使用对象的“对象标识符”来添加“自动一致性”。在下面的代码中,我实现了一个可重用的类来自动执行此操作。
注意,Swift 4.2改变了Hashable的实现方式,所以不再覆盖hashValue,而是覆盖hash(into:)

open class HashableClass {
    public init() {}
}

// MARK: - <Hashable>

extension HashableClass: Hashable {

    public func hash(into hasher: inout Hasher) {
         hasher.combine(ObjectIdentifier(self))
    }
    
    // `hashValue` is deprecated starting Swift 4.2, but if you use 
    // earlier versions, then just override `hashValue`.
    //
    // public var hashValue: Int {
    //    return ObjectIdentifier(self).hashValue
    // }
}

// MARK: - <Equatable>

extension HashableClass: Equatable {

    public static func ==(lhs: HashableClass, rhs: HashableClass) -> Bool {
        return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
    }
}

要使用,只需使用类和子类HashableClass,然后一切都应该正常工作!

class MyClass: HashableClass {

}
envsm3lx

envsm3lx3#

TL; DR:

不是用Hashable协议扩展类,然后复制每个类的实现,而是扩展Hashable协议本身,将其约束为AnyObject,然后将共享实现放在那里。这样,只需使类符合Hashable,它们就会自动获得共享实现。

细节...

正如这里的公认答案所示,一些实现将使用类类型上的扩展来实现Hashable。这种方法的问题是***您必须为您定义的每个类型复制实现***违反了DRY(即不要重复自己)原则。
下面是这种方法的一个例子......

extension SomeClass : Hashable {

    func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }

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

extension SomeOtherClass : Hashable {

    func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }

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

extension YetAnotherClass : Hashable {

    func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }

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

有很多重复的代码!
我的建议是相反的。不是扩展单个类类型,而是扩展Hashable协议本身,然后将其约束到AnyClass并将实现放在那里。这样做会自动将该实现应用到***所有***类,这些类只指定符合Hashable协议,而不需要特定于类的实现。
这是这种方法的样子...

extension Hashable where Self: AnyObject {

    func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }
}
    
extension Equatable where Self: AnyObject {

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

注意:虽然你可以直接将相等运算符添加到Hashable扩展中,但通过将其应用到Equatable上的扩展中(Hashable隐式地符合该扩展),你可以使用相同的技术将示例相等应用到类类型中,即使你不想或不需要哈希能力。
有了以上两点,我们现在可以这样做了...

extension SomeClass : Hashable {}
extension SomeOtherClass : Hashable {}
extension YetAnotherClass : Hashable {}

没有重复的代码。只是符合协议。
当然,如前所述,Hashable还隐式地提供Equatable,所以这些现在也都可以工作了...

let a = SomeClass()
let b = a
let msg = (a == b)
    ? "They match! :)"
    : "They don't match. :(" 
print(msg)

// Prints They match! :)
  • 注意:这不会干扰直接实现Hashable的类,因为类特定的定义更显式,因此它优先,它们可以和平共存。*

隐含相等

更进一步说,只谈Equatable,如果要***隐式***(也就是说,不需要手动遵守协议)使所有对象类型都实现Equatable,使用标识来测试相等性--我个人很想知道为什么默认情况下不是这样(如果需要,您仍然可以按类型重写它)--您可以使用带有全局定义的相等运算符的泛型,再次将约束设置为AnyObject
这是密码...

func == <T:AnyObject>(lhs: T, rhs: T) -> Bool {
    return lhs === rhs
}

func != <T:AnyObject>(lhs: T, rhs: T) -> Bool {
    return !(lhs == rhs)
}
  • 注意:如果你走这条路,为了完整起见,你也应该像我在这里所做的那样定义!=操作符,与在扩展中定义==操作符不同,编译器 * 不会 * 自动为你合成它,所以你必须显式地做这件事。*

有了以上内容,您现在可以执行以下操作...

class Foo {} // Note no protocols or anything else specified. Equality 'just works' for classes

let a = Foo()
let b = a
var msg = (a == b)
    ? "They match! :)"
    : "They don't match. :(" 
print(msg)

// Prints They match! :)

let c = Foo()
var msg = (a == c)
    ? "They don't match! :)"
    : "They match. :(" 
print(msg)

// Prints They don't match! :)

如上所述,您仍然可以使用类型特定版本的equality,因为它们比AnyObject版本更具体,因此可以与上面提供的默认reference-equality和平共存。
下面的示例假设上述内容已经就绪,但仍然只基于idLaa定义了一个显式版本的等式。

class Laa {
    init(_ id:String){
        self.id = id
    }
    let id:String
}

// Override implicit object equality and base it on ID instead of reference
extension Laa : Equatable {

    static func == (lhs:Laa, rhs:Laa) -> Bool {
        return lhs.id == rhs.id
    }
}

注意事项:如果要覆盖相等性的对象也实现了Hashable则必须确保对应的哈希值也相等根据定义,相等的对象应生成相同的哈希值。

也就是说,如果您将Hashable扩展限制为本文开头的AnyObject,并简单地用Hashable标记您的类,您将获得基于对象标识的默认实现,因此它不会匹配共享相同ID的不同类示例(因此被认为是相等的),所以你必须明确地确保也实现了散列函数。编译器不会为你捕捉到这个
同样,只有当你重写等式 * 并且 * 你的类实现了Hashable时,你才需要这么做。
Hee上实现hashable以遵循相等/hashable关系:

extension Hee : Hashable {

    func hash(into hasher: inout Hasher) {
        hasher.combine(id) // Easiest to simply use ID here since that's what Equatable above is based on
    }
}

最后,这里是你如何使用它...

let hee1 = Hee("A")
let hee2 = Hee("A")
let msg2 = (hee1 == hee2)
    ? "They match! :)"
    : "They don't match. :("
print(msg2)

// Prints 'They match! :)'

let set = Set<Hee>()
set.append(hee1)
set.append(hee2)
print("Set Count: \(set.count)")

// Prints 'Set Count: 1'
hts6caw3

hts6caw34#

Swift 5准系统

对于使类可散列的基本实现,我们必须同时遵守EquatableHashable协议,如果我们很好地了解我们的对象,我们可以确定使用哪个或哪些属性使它可相等和可散列。

class CustomClass: Equatable, Hashable {
    let userId: String
    let name: String
    let count: Int

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

    /* The Equatable protocol requires us to establish a predicate that will
     determine if two instances of this type are equal or unequal based on
     on what we consider equal and unequal. Here, userId makes the most sense
     since we know they'll always be unique. And so now when we compare two
     instances of this type, this is the function that will make that
     comparison. */
    static func == (lhs: CustomClass, rhs: CustomClass) -> Bool {
        return lhs.userId == rhs.userId
    }

    /* The Hashable protocol is similar to Equatable in that it requires us to
       establish a predicate that will determine if two instances of this type
       are equal or unequal, again based on what we consider equal and
       unequal, but here we must feed that property (or properties) into a
       function that will produce the object's hash value. And this makes sense
       because the purpose of a hash is to serve as a unique identifier. And,
       again, userId makes the most sense since we know they'll always be unique.
       However, if userId was not unique, then we could combine multiple
       properties to (hopefully) generate a unique hash. Or we could simply
       generate a UUID within each instance and use that property solely. */
    func hash(into hasher: inout Hasher) {
        hasher.combine(userId)
        //hasher.combine(name) If userId was not unique, we could add this.
        //hasher.combine(count) If that's still not enough, we could add even more.
        
        // However, at this point, consider generating a UUID and using that.
    }
}
j13ufse2

j13ufse25#

另一种选择是为AnyObject实现对HashableEquatable协议的扩展,似乎达到了与您在Java中提到的类似的效果。
它为项目中的所有类添加了一个默认行为,与只为指定的类添加这样的行为相比,它并不是更好的选择。所以,我只是为了完整性才提到它:

class HashableClass: Hashable {

}

extension Hashable where Self: AnyObject{

  func hash(into hasher: inout Hasher) {

     hasher.combine(ObjectIdentifier(self))
   }
}

extension Equatable where Self: AnyObject{

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

现在,如果你想为你的类实现特定的逻辑,你仍然可以这样做,就像这样:

class HashableClass { //deleted the Hashable conformance

}
extension HashableClass : Hashable{

   func hash(into hasher: inout Hasher) {

      //your custom hashing logic
   }
}

为了避免向AnyObject添加默认行为,可以声明另一个协议,并且可以添加仅与该新协议相关的扩展:

protocol HashableClass : AnyObject{

}

class SomeClass: Hashable, HashableClass {

 }

 extension Hashable where Self: HashableClass{

   func hash(into hasher: inout Hasher) {

      hasher.combine(ObjectIdentifier(self))
   }
 }

extension Equatable where Self: HashableClass{

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

相关问题