Я начал учиться быстро после Java. В Java я могу использовать любой объект в качестве ключа для HashSet, потому что он имеет значения по умолчанию hashCode
и equals
на основе идентификатора объекта. Как добиться такого же поведения в Swift?
Swift, как реализовать протокол Hashable на основе ссылки на объект?
Ответ 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
}
}