Renaming hash
to hashValue
in the overlay would have worked before, but given that 4.2 emits the warning forcing people to migrate to hash
, renaming it now would just add to the confusion. 
There are two deeper issues I suggest we should talk about.
- In Swift 4.2+, types should conform to
Hashable
by implementing hash(into:)
, not hashValue
. This is not currently possible for NSObject subclasses.
- There is a related, possibly even more serious confusion between
isEqual(_:)
and ==
that remains unresolved.
Consider the code below. Similar code anecdotally exists in some production codebases today.
class Foo: NSObject {
var i: Int
init(_ value: Int) {
self.i = value
super.init()
}
override var hashValue: Int { // THIS IS BROKEN, DO NOT USE
return i.hashValue
}
static func ==(left: HashClass, right: HashClass) -> Bool { // THIS IS BROKEN, DO NOT USE
return left.i == right.i
}
}
First, let's recap the problem with that hashValue
override: while it works okay in Swift, it does not get picked up by Objective-C code, which calls hash
. Foo
leaves hash
with its original NSObject
definition, which is based on object identity.
As we've seen, the Swift 4.2 compiler emits the following warning for this: (line break added for clarity)
warning: override of 'NSObject.hashValue' is deprecated;
override 'NSObject.hash' to get consistent hashing behavior
This warning spells out the correct solution, hopefully resolving the confusion. (We expect to turn this into a hard error by 5.0.) Sadly the warning doesn't come with a fix-it yet. Adding one would make a good starter bug.
Now let's turn to the ==
implementation, which is even more problematic. Never mind Objective-C compatibility -- it doesn't even work in Swift!
Foo(42) == Foo(42) // false
This is because it's not Foo that implements Equatable, but rather it's NSObject. And NSObject's definition of ==
is non-overridable by definition -- you cannot override operators in Swift.
The ==
above just sits there and confuses anyone reading the code. It looks like a valid definition, but it actually does nothing. I think it may be a good idea to warn about such decoy operator definitions in any context.
(Frustrating note: NSObject also has an isEqual(to:)
method, which is confusingly similar to isEqual(_:)
, but it is a different thing. Mistaking one for the other can lead to long debugging sessions.)
I'm not yet sure how we can enable hash(into:)
overrides on NSObject subclasses. It would be possible to do it by adding extra complications to the compiler's Hashable
synthesis:
extension NSObject {
open func hash(into hasher: inout Hasher) {
hasher.combine(self.hash)
}
}
class Foo: NSObject {
let i: Int = 0
override func hash(into hasher: inout Hasher) { // Supplied manually
hasher.combine(i)
}
override var hash: Int { // Automatically force-synthesized
var hasher = Hasher()
hasher.combine(self)
return hasher.finalize()
}
override func isEqual(_ other: Any) -> Bool {
return i == (other as? Foo)?.i
}
}
However, this is probably unwise -- Hashable
synthesis is already far too complicated, and this scheme could cause problems for subclasses of Swift classes defined in Objective-C code.