Using memory addresses for hashing

Today, Tim talks about conforming reference types to a hashable program. This enables you to use specific class instances for set elements and dictionary keys. Read on to learn more and don’t forget to follow Tim on Twitter.

For reference types, it often makes sense to consider two variables to be equal when they reference the same address in memory. Say you have a UIView instance visible on screen, for example. You could create a copy of that view with the exact same properties (size, position, background color, etc.) but modifying the copy will obviously have no visible effect.

Testing whether two variables refer to the same instance is easy using the identity operators (=== and !==). However, this does not allow you to put instances of an arbitrary class in a Set, or use them as keys in a Dictionary, since that requires conforming to the Hashable protocol.

Interestingly, classes that inherit from NSObject don’t face this problem: NSObject implements == and hashValue, the two requirements of the Hashable protocol, using isEqual(_:) and hash respectively. The default implementation of isEqual uses pointer equality, and the default value for hash is the instance’s memory address.

In order to use memory addresses for hashing without inheriting from NSObject (which you shouldn’t), you can use the ObjectIdentifier type that is part of the Swift standard library:

final class Foo: Hashable {
    static func == (left: Foo, right: Foo) -> Bool {
        return left === right
    }
    
    var hashValue: Int {
        return ObjectIdentifier(self).hashValue
    }
}

Note that an instance’s memory address can (and will) change across different executions of the program, but that’s fine: the Hashable protocol doesn’t require hash values to be stable across different executions.

Of course, you can put this code in a protocol extension to make it reusable:

protocol PointerHashable: class, Hashable {}

extension PointerHashable {
    static func == (left: Self, right: Self) -> Bool {
        return left === right
    }
    
    var hashValue: Int {
        return ObjectIdentifier(self).hashValue
    }
}

Now, all it takes for a custom class to be usable as set elements and dictionary keys is conformance to PointerHashable. But be careful: the Hashable protocol requires that two instances that are considered equal with == also have the same hash value, so if you are implementing == using custom logic, you will need a custom hashValue implementation as well.

Comments are closed.