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

时间:2016-01-10 13:12:57

标签: swift hashtable

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

5 个答案:

答案 0 :(得分:26)

如果您正在使用类而不是结构,则可以使用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
    }
}

答案 1 :(得分:7)

在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)
    }

    // `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 {

}

答案 2 :(得分:5)

这是另一种仅基于协议(即没有基类)的解决方案,但它简化了您的使用方式,同时仍然允许用户选择使用。您使用受约束的扩展名同时遵守HashableEquatable协议。

几个实现将使用类扩展来采用协议一致性,就像这样...

extension SomeClass : Hashable {

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

但是相反,我们通过扩展协议本身来反转它,然后使用约束AnyClass,这使得该实现适用于 所有 类,这些类仅指定符合协议,无需特定于类的实现...

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 Foo : Hashable {} // Defining with the class definition
var fooToStringLookup:[Foo:String] = [:]

class Laa {}
extension Laa : Hashable {} // Adding to an existing class via an extension
var laaToIntLookup:[Laa:Int] = [:]

当然Hashable也会隐含地给您Equatable,所以这些现在都可以使用...

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

注意:这不会干扰直接实现Hashable的类,因为特定于类的定义更加明确,因此它具有优先权并且可以和平共处。

如果想使 all 所有对象类型实现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 Laa {} // Note no protocols or anything else specified. Equality 'just works'

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

// Prints 'They match! :)'

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

// Prints 'They don't match! :)'

如上所述,您仍然可以使用特定于类型的相等版本。这是因为它们更加具体,因此优先于AnyObject版本,因此可以与上面提供的默认引用平等和平共处。

下面是一个示例,其中假设已完成上述操作,但仍仅基于Heeid定义了一个相等的显式版本。

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

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

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

注意:如果您要覆盖相等的对象也实现了Hashable,则您必须确保相应的哈希值也相等,如下所示:定义,相等的对象应该产生相同的哈希值。

也就是说,如果您将Hashable扩展名从本文的开头限制为AnyObject,并简单地用Hashable标记您的类,则将获得基于以对象的身份为准,因此它不会与共享相同ID(因此被认为是相等的)的不同类实例匹配,因此您必须明确确保也实现哈希函数。 编译器不会为您捕获

同样,仅当您重写等式 并且您的类实现Hashable时,才需要这样做。如果是这样,请按以下步骤操作...

Hee上实施可哈希处理以遵循相等/可哈希关系:

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'

答案 3 :(得分:1)

另一种选择是为Hashable实现EquatableAnyObject协议的扩展。似乎实现了与您在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
   }
}

答案 4 :(得分:1)

迅速扫清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
    }
    
    /* Equatable protocol simply means establishing a predicate to
       determine if two instances of the same type are equal or unequal based
       on what we consider equal and unequal. In this class, userId makes
       the most sense so if we were to compare two instances (left-hand-side
       versus right-hand-side), we would compare their userId values. When
       it comes time to compare these objects with each other, the machine
       will look for this function and use it to make that determination. */
    static func == (lhs: CustomClass, rhs: CustomClass) -> Bool {
        return lhs.userId == rhs.userId
    }
    
    /* Hashable protocol is similar to Equatable in that it requires us to
       establish a predicate to determine if two instances of the same
       type are equal or unequal, again based on what we consider equal and
       unequal. But in this protocol we must feed that property into a
       function which will produce the object's hash value. And again, userId
       makes the most sense because each instance carries a unique value. If
       userId was not unique, we could combine multiple properties to
       generate a unique hash. */
    func hash(into hasher: inout Hasher) {
        hasher.combine(userId)
        //hasher.combine(name) if userId was not unique, we could have added this
    }
}