7

I have a "get hash of string by String.hashValue" code, that I added it below. This code worked well in Xcode 9.4.1.

Worked well means that whenever I close app and re-open it, the result of hashValue is same (unique)

private func cacheName(of url: String) -> String {
    // The url is url of a png image, for example www.imageurl.com/image.png
    return "\(url.hashValue)"
}

When I build my Project in Xcode 10 the result changes everytime I restart the app (close and open app again). The version of iOS, device, Swift version is same. So I think the problem is Xcode 10 has change something that effect to the hashValue (maybe configure when build app ??)

If I use the String.hash instead of, it works well. But in the previous version, I saved the hashValue result, so I don't want to change it.

How can I keep the result of String.hashValue unique in every time. Or any suggestion would be appreciated

piet.t
  • 11,718
  • 21
  • 43
  • 52
Quoc Nguyen
  • 2,839
  • 6
  • 23
  • 28
  • 11
    https://developer.apple.com/documentation/swift/hashable/1540917-hashvalue ALWAYS (since Swift 1.0) stated that “Hash values are not guaranteed to be equal across different executions of your program. Do not save hash values to use during a future execution." – Martin R Sep 21 '18 at 09:26

1 Answers1

23

Swift 4.2 has implemented SE-0206: Hashable Enhancements. This introduces a new Hasher struct that provides a randomly seeded hash function. That's why the hashing results differ everytime (since the seed is random). You can find the implementation of the Hasher struct, with the generation of a random seed, here.

If you want a stable hash value associated to a String, accross devices and app lauches, you could use this solution by Warren Stringer:

let str = "Hello"

func strHash(_ str: String) -> UInt64 {
    var result = UInt64 (5381)
    let buf = [UInt8](str.utf8)
    for b in buf {
        result = 127 * (result & 0x00ffffffffffffff) + UInt64(b)
    }
    return result
}

strHash(str)     //177798404835661

Or have these couple of extensions defined on String:

extension String {
    var djb2hash: Int {
        let unicodeScalars = self.unicodeScalars.map { $0.value }
        return unicodeScalars.reduce(5381) {
            ($0 << 5) &+ $0 &+ Int($1)
        }
    }

    var sdbmhash: Int {
        let unicodeScalars = self.unicodeScalars.map { $0.value }
        return unicodeScalars.reduce(0) {
            (Int($1) &+ ($0 << 6) &+ ($0 << 16)).addingReportingOverflow(-$0).partialValue
        }
    }
}

"Hello".djb2hash    //210676686969
"Hello".sdbmhash    //5142962386210502930

(This is executed on Xcode 10, Swift 4.2)

ielyamani
  • 17,807
  • 10
  • 55
  • 90