Difference Between Swift's Hash and Hashvalue

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 the
NSObject 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 the
isEqual: 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 the isEqual: 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 the NSObject class is equivalent to an identity check by pointer equality. You can override isEqual: 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



Leave a reply



Submit