Skip to content

Commit d03786e

Browse files
committed
[ObjectiveC] NSObject: Clarify hashing implementation
NSObject defines its own interface for hashing and equality. Subclasses must implement custom hashing by overriding the hash property, or NSSet and NSDictionary will not work correctly. Unfortunately, we currently define NSObject.hashValue and NSObject.hash(into:) as overridable, which leads people to accidentally override one of these instead of hash. hashValue is explicitly declared open, while hash(into:) is automatically synthesized as such. Making hashValue non-open is a potentially source breaking change, but we can at least provide an explicit, non-overridable definition for hash(into:), and update the documentation to explain the contract.
1 parent 3f859a3 commit d03786e

File tree

1 file changed

+34
-3
lines changed

1 file changed

+34
-3
lines changed

stdlib/public/SDK/ObjectiveC/ObjectiveC.swift

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,25 +195,56 @@ public var NO: ObjCBool {
195195
//===----------------------------------------------------------------------===//
196196

197197
// NSObject implements Equatable's == as -[NSObject isEqual:]
198-
// NSObject implements Hashable's hashValue() as -[NSObject hash]
198+
// NSObject implements Hashable's hashValue as -[NSObject hash]
199199
// FIXME: what about NSObjectProtocol?
200200

201201
extension NSObject : Equatable, Hashable {
202+
/// Returns a Boolean value indicating whether two values are
203+
/// equal. `NSObject` implements this by calling `lhs.isEqual(rhs)`.
204+
///
205+
/// Subclasses of `NSObject` can customize Equatable conformance by overriding
206+
/// `isEqual(_:)`. If two objects are equal, they must have the same hash
207+
/// value, so if you override `isEqual(_:)`, make sure you also override the
208+
/// `hash` property.
209+
///
210+
/// - Parameters:
211+
/// - lhs: A value to compare.
212+
/// - rhs: Another value to compare.
202213
public static func == (lhs: NSObject, rhs: NSObject) -> Bool {
203214
return lhs.isEqual(rhs)
204215
}
205216

206217
/// The hash value.
207218
///
219+
/// `NSObject` implements this by returning `self.hash`. Subclasses can
220+
/// customize hashing by overriding the `hash` property.
221+
///
208222
/// **Axiom:** `x == y` implies `x.hashValue == y.hashValue`
209223
///
210224
/// - Note: the hash value is not guaranteed to be stable across
211225
/// different invocations of the same program. Do not persist the
212226
/// hash value across program runs.
213-
@objc
214-
open var hashValue: Int {
227+
@objc open // FIXME: Should be @nonobjc public. rdar://problem/42623458
228+
var hashValue: Int {
215229
return hash
216230
}
231+
232+
/// Hashes the essential components of this value by feeding them into the
233+
/// given hasher.
234+
///
235+
/// NSObject implements this by feeding `self.hash` to the hasher. Subclasses
236+
/// can customize hashing by overriding the `hash` property.
237+
public func hash(into hasher: inout Hasher) {
238+
// FIXME: We should combine self.hash here, but hashValue is currently
239+
// overridable.
240+
hasher.combine(hashValue)
241+
}
242+
243+
public func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
244+
// FIXME: We should use self.hash here, but hashValue is currently
245+
// overridable.
246+
return self.hashValue._rawHashValue(seed: seed)
247+
}
217248
}
218249

219250
extension NSObject : CVarArg {

0 commit comments

Comments
 (0)