Difference between Swift's hash and hashValue
hash
is a required property in the NSObject
protocol, which groups methods that are fundamental to all Objective-C objects, so that predates Swift.
The default implementation just returns the objects address,
as one can see in
NSObject.mm, but one can override the property
in NSObject
subclasses.
hashValue
is a required property of the Swift Hashable
protocol.
Both are connected via a NSObject
extension defined in the
Swift standard library in
ObjectiveC.swift:
extension NSObject : Equatable, Hashable {
/// The hash value.
///
/// **Axiom:** `x == y` implies `x.hashValue == y.hashValue`
///
/// - Note: the hash value is not guaranteed to be stable across
/// different invocations of the same program. Do not persist the
/// hash value across program runs.
open var hashValue: Int {
return hash
}
}
public func == (lhs: NSObject, rhs: NSObject) -> Bool {
return lhs.isEqual(rhs)
}
(For the meaning of open var
, see What is the 'open' keyword in Swift?.)
So NSObject
(and all subclasses) conform to the Hashable
protocol, and the default hashValue
implementation
return the hash
property of the object.
A similar relationship exists between the isEqual
method of theNSObject
protocol, and the ==
operator from the Equatable
protocol: NSObject
(and all subclasses) conform to the Equatable
protocol, and the default ==
implementation
calls the isEqual:
method on the operands.
Swift - A Decimal's hashValue is the same for X == -X, cannot be used for comparing hashValues
Identical objects must have the same hash value, but not the other way around: Distinct objects can have the same hash value. Testing for equality must be done with ==
and never rely on the hash value alone.
In this particular case note that there are more than 264 Decimal
values, so that it would actually be impossible to assign different hash values to all of them. (Similarly for strings, arrays, dictionaries, ...).
If you have a custom struct containing Decimal
(and possibly other) properties then the implementation of the Equatable
and Hashable
protocol should look like this:
struct Foo: Hashable {
let value: Decimal
let otherValue: Int
static func == (lhs: Foo, rhs: Foo) -> Bool {
return lhs.value == rhs.value && lhs.otherValue == rhs.otherValue
}
func hash(into hasher: inout Hasher) {
hasher.combine(value)
hasher.combine(otherValue)
}
}
Note that if all stored properties are Hashable
then the compiler can synthesize these methods automatically, and it is sufficient to declare conformance:
struct Foo: Hashable {
let value: Decimal
let otherValue: Int
}
Remark: I assume that the behaviour is inherited from the Foundation type NSDecimalNumber
. With Xcode 11 beta (Swift 5.1) x
and -x
have different hash values as Decimal
, but the same hash value as NSDecimalNumber
:
let d1: Decimal = 123
let d2: Decimal = -123
print(d1.hashValue) // 1891002061093723710
print(d2.hashValue) // -6669334682005615919
print(NSDecimalNumber(decimal: d1).hashValue) // 326495598603
print(NSDecimalNumber(decimal: d2).hashValue) // 326495598603
(Your values may vary since hash values are randomized as of Swift 4.2.) But the above still applies: There can always be collisions, and one cannot rely on different values having different hashes.
NSObject subclass in Swift: hash vs hashValue, isEqual vs ==
NSObject
already conforms to the Hashable
protocol:
extension NSObject : Equatable, Hashable {
/// The hash value.
///
/// **Axiom:** `x == y` implies `x.hashValue == y.hashValue`
///
/// - Note: the hash value is not guaranteed to be stable across
/// different invocations of the same program. Do not persist the
/// hash value across program runs.
public var hashValue: Int { get }
}
public func ==(lhs: NSObject, rhs: NSObject) -> Bool
I could not find an official reference, but it seems that hashValue
calls the hash
method from NSObjectProtocol
, and ==
calls theisEqual:
method (from the same protocol). See update at the
end of the answer!
For NSObject
subclasses, the correct way seems to be
to override hash
and isEqual:
, and here is an experiment which
demonstrates that:
1. Override hashValue
and ==
class ClassA : NSObject {
let value : Int
init(value : Int) {
self.value = value
super.init()
}
override var hashValue : Int {
return value
}
}
func ==(lhs: ClassA, rhs: ClassA) -> Bool {
return lhs.value == rhs.value
}
Now create two different instances of the class which are considered
"equal" and put them into a set:
let a1 = ClassA(value: 13)
let a2 = ClassA(value: 13)
let nsSetA = NSSet(objects: a1, a2)
let swSetA = Set([a1, a2])
print(nsSetA.count) // 2
print(swSetA.count) // 2
As you can see, both NSSet
and Set
treat the objects as different.
This is not the desired result. Arrays have unexpected results as well:
let nsArrayA = NSArray(object: a1)
let swArrayA = [a1]
print(nsArrayA.indexOfObject(a2)) // 9223372036854775807 == NSNotFound
print(swArrayA.indexOf(a2)) // nil
Setting breakpoints or adding debug output reveals that the overridden==
operator is never called. I don't know if this is a bug or
intended behavior.
2. Override hash
and isEqual:
class ClassB : NSObject {
let value : Int
init(value : Int) {
self.value = value
super.init()
}
override var hash : Int {
return value
}
override func isEqual(object: AnyObject?) -> Bool {
if let other = object as? ClassB {
return self.value == other.value
} else {
return false
}
}
}
For Swift 3, the definition of isEqual:
changed to
override func isEqual(_ object: Any?) -> Bool { ... }
Now all results are as expected:
let b1 = ClassB(value: 13)
let b2 = ClassB(value: 13)
let nsSetB = NSSet(objects: b1, b2)
let swSetB = Set([b1, b2])
print(swSetB.count) // 1
print(nsSetB.count) // 1
let nsArrayB = NSArray(object: b1)
let swArrayB = [b1]
print(nsArrayB.indexOfObject(b2)) // 0
print(swArrayB.indexOf(b2)) // Optional(0)
Update: The behavior is documented in the book "Using Swift with Cocoa and Objective-C", under "Interacting with Objective-C API":
The default implementation of the
==
operator invokes theisEqual:
method, and the default implementation of the===
operator checks pointer equality. You should not override the equality or identity operators for types imported from Objective-C.The base implementation of the
isEqual:
provided by theNSObject
class is equivalent to an identity check by pointer equality. You can overrideisEqual:
in a subclass to have Swift and Objective-C APIs determine equality based on the contents of objects rather than their identities.
The book is available in the Apple Book app.
It was also documented on Apple's website but was removed, and is still visible on the WebArchive snapshot of the page.
Hash different for the same object, Swift, Hashable
Hash randomization was enforced in Swift 4.2, with the implementation of SE 0206 Hashable Enhancements. From the proposal:
However, Hasher may generate entirely different hash values in other executions, even if it is fed the exact same byte sequence. This randomization is a critical feature, as it makes it much harder for potential attackers to predict hash values. Hashable has always been documented to explicitly allow such nondeterminism.
In addition, it allows the actual implementation to be changed (e.g. improved) in the Swift standard library, without breaking compatibility.
For debugging purposes the hash randomization can be disabled by defining the SWIFT_DETERMINISTIC_HASHING environment variable with a value of 1.
The implementation of the Swift standard hasher can be found in the open source repository:
- https://github.com/apple/swift/blob/master/stdlib/public/core/Hasher.swift
- https://github.com/apple/swift/blob/master/stdlib/public/core/SipHash.swift
It is based on SipHash.
What is the use of Hashable and Equatable in Swift? When to use which?
When you conform to Hashable
, you provide a method that returns the hash value of self
.
When you conform to Equatable
, you provide a method that returns whether the given object and self
are equal.
They seem to serve two very different purposes, why does Hashable
inherit Equatable
? Because the hash values for two equal objects are equal!
What can and can't you do with Hashable
and Equatable
?
Equatable
has more limited uses than Hashable
. It can only compare the equality of two objects, and that's it.
For Hashable
, because you can get a number that represents the object, you can kind of treat the objects as numbers. You can compare the objects: whether it is less than, greater than, or equal to another object, just like you do with numbers:
if objA.hashValue > objB.hashValue
This also means you can sort objects with Hashable
.
Last but not least, you can use Hashable
objects as keys for maps! this is because maps' keys cannot duplicate, so how can the system check whether you put a duplicate item in it? It uses the hash values of the keys!
Related Topics
Enable + Disable Auto-Layout Constraints
Codable and Xmlparser in Swift
Xcode Error: Missing Required Module 'Firebase'
Difference Between Packed VS Normal Data Type
iOS Screen Sharing (Using Replaykit) Using Webrtc in Swift
How to Define an Enum as a Subset of Another Enum's Cases
Get Path to Swift Script from Within Script
Arkit - How to Put 3D Object on Qrcode
Seeking an "Exit" Equivalent in Swift
Swift Remove Object from Realm
Difference Between Switch Cases "@Unknown Default" and "Default" in Swift 5
Unsafemutablepointer<Uint8> to [Uint8] Without Memory Copy
Calculating Angle Between Two Points on Edge of Circle Swift Spritekit