How does equality work for NSObject subclasses, and why do two “equal” objects stay distinct in a Set?
Predict first
How many elements are in the set?
class Player: NSObject {
let id: Int
init(_ id: Int) { self.id = id }
}
print(Set([Player(1), Player(1)]).count)
- 1
- 2
Predict first
How many elements now?
class Player: NSObject {
let id: Int
init(_ id: Int) { self.id = id }
override func isEqual(_ o: Any?) -> Bool { (o as? Player)?.id == id }
override var hash: Int { id }
}
print(Set([Player(1), Player(1)]).count)
- 1
- 2
Mental model. Two employees with the same name are still different badges until HR agrees both the name check and the badge number match.
NSObject conforms to Equatable and Hashable through the Objective-C methods isEqual(_:) and hash. By default both use identity: two different instances are never equal. So Set([Player(1), Player(1)]) holds two elements even if their ids match.
To get value equality, override both isEqual(_:) and hash, using the same fields in each. Overriding only isEqual breaks the contract (equal objects with different hashes), and the set still keeps both. Declaring a Swift == on the subclass is not enough: Set, Dictionary and Objective-C collections go through hash and isEqual. This is the same rule as plain Hashable, with one extra place to get it wrong.
Without overrides, equality is identity, so two instances are two elements.
Both isEqual and hash now use id, so the two players collapse into one element. Remove hash and it goes back to 2.
Common traps
- Overriding
isEqualwithouthash. Equal objects hash differently, so sets and dictionaries misbehave. - Adding
static func ==only. The Objective-C collections never call it.
They might follow up with
- How does
NSSetdiffer fromSet? It usesisEqualandhashtoo, and holds objects only. - When should a class be value-equal at all? When it represents a value, such as a model with an id, and not an entity with identity.
Related questions
- 2.1.2 What must Hashable guarantee, and why does iteration order change between launches?
- 2.9.1 What is toll-free bridging, and how do you convert between Swift and Foundation types?
← Which Foundation types are value types in Swift, which are classes…