14

Dictionary key requires Hashable conformance:

class Test {}
var dictionary = [Test: String]() // Type 'Test' dies not conform to protocol 'Hashable'

class Test: NSObject {}
var dictionary = [Test: String]() // Works

How to get address of pure Swift class instance to use as hashValue?

Kirsteins
  • 27,065
  • 8
  • 76
  • 78

3 Answers3

21

Equality can be implemented as object identity, i.e. a == b iff a and b refer to the same instance of the class, and the hash value can be build from the ObjectIdentifier (which is the same for identical objects, compare e.g. Difference between using ObjectIdentifier() and '===' Operator):

For Swift 4.2 and later:

class Test : Hashable {
    static func ==(lhs: Test, rhs: Test) -> Bool {
        return lhs === rhs
    }

    public func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }
}

For Swift 3:

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

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

For Swift 2.3 and earlier, you can use

/// Return an UnsafePointer to the storage used for `object`.  There's
/// not much you can do with this other than use it to identify the
/// object
func unsafeAddressOf(object: AnyObject) -> UnsafePointer<Void>

i.e.

class Test : Hashable {
    var hashValue: Int { return unsafeAddressOf(self).hashValue }
}

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

Example:

var dictionary = [Test: String]()
let a = Test()
let b = Test()
dictionary[a] = "A"
print(dictionary[a]) // Optional("A")
print(dictionary[b]) // nil

implement the Equatable protocol.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • `unsafeAddressOf` is what I have been looking for. Thanks! – Kirsteins May 20 '15 at 10:18
  • Will there be a case where `unsafeAddressOf` would return a different address for the same variable? I seem to have run into this problem and cannot reproduce it anymore. The address would say one thing at first and then it would change, as if Swift moved the variable in memory elsewhere. – pixelfreak Aug 08 '16 at 08:52
  • Not for instances of a *class*. But see http://stackoverflow.com/questions/32638879/swift-strings-and-memory-addresses for a case where it would happen for certain *value types* which are implicitly bridged to some NSObject. – Martin R Aug 08 '16 at 08:57
  • 1
    The property declaration can be tightened up to `lazy var hashValue: Int = ObjectIdentifier(self).hashValue` – Christopher Swasey Oct 24 '16 at 15:24
1

Swift 3

This based on the great code snippet in Martin R's answer with insightful comment from Christopher Swasey

class Test: Hashable, Equatable {
    lazy var hashValue: Int = ObjectIdentifier(self).hashValue

    static func ==(lhs: Test, rhs: Test) -> Bool {
        return lhs === rhs
    }
}

var dictionary = [Test: String]()
let a = Test()
let b = Test()
dictionary[a] = "A"
print(dictionary[a]) // Optional("A")
print(dictionary[b]) // nil
Community
  • 1
  • 1
neoneye
  • 50,398
  • 25
  • 166
  • 151
0

If you don't want or cannot implement Hashable for some reason it's easy to use an Objective C helper:

(long )getPtr:(SomeType* )ptr { return (long )ptr; }

long maps to Swift Int and can be perfectly used as a Swift Dictionary key on both 32 and 64bit architectures. It was the fastest solution which I found while profiling different approaches, including unsafeAddress. In my case performance was the main criteria.

Vadim
  • 567
  • 6
  • 13