Swift, как реализовать протокол Hashable на основе ссылки на объект?

Я начал учиться быстро после Java. В Java я могу использовать любой объект в качестве ключа для HashSet, потому что он имеет значения по умолчанию hashCode и equals на основе идентификатора объекта. Как добиться такого же поведения в Swift?

Ответ 1

Если вы работаете с классами, а не с структурами, вы можете использовать структуру ObjectIdentifier. Обратите внимание, что вам также необходимо определить == для вашего класса, чтобы соответствовать Equatable (Hashable требует его). Он будет выглядеть примерно так:

class MyClass: Hashable { }

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

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

Ответ 2

В Swift тип должен соответствовать Hashable и Equatable чтобы его можно было использовать в структуре данных, такой как Dictionary или Set. Однако вы можете добавить "автоматическое соответствие", используя "идентификатор объекта" объекта. В приведенном ниже коде я реализовал многократно используемый класс, чтобы сделать это автоматически.

Обратите внимание, что 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 func ==(lhs: HashableClass, rhs: HashableClass) -> Bool {
    return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}

Чтобы использовать, просто возьмите свой класс и подкласс HashableClass, тогда все должно просто работать!

class MyClass: HashableClass {

}

Ответ 3

Другой вариант - реализовать расширение для Hashable и Equatable протоколов для AnyObject. Похоже, что достигается эффект, подобный тому, который вы упомянули в 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
   }
}