We have a custom Swift class and want to use it as the key of a dictionary. How do we do that?
Our custom Swift class represents a student with a unique studentID
, a firstName
and a lastName
.
class Student { let studentID: Int var firstName: String var lastName: String init(studentID: Int, firstName: String, lastName: String) { self.studentID = studentID self.firstName = firstName self.lastName = lastName } }
We have a track and field competition where a group of students competes in a 100-metre dash. We want to store the results of the races in a dictionary, where Student
objects are mapped to the students’ 100-metre times. We want to write code like this:
let larry = Student(studentID: 170523, firstName: "Larry", lastName: "Palmer") var results: [Student: Double] = [:] results[larry] = 10.75 print("Larry's time was \(results[larry])")
We can use a custom class as the key type of dictionaries if the custom class implements the Hashable
protocol. The Hashable
protocol inherits from the Equatable
protocol. Hence, we must implement both the Hashable
and the Equatable
protocol.
The Hashable
protocol requires us to assign an integer value to the instance property hashValue
. The property studentID
is the ideal candidate, because it has type Int
and it is unique for all students. The parts needed by the Hashable
are shown in bold face.
class Student: Hashable { let studentID: Int var firstName: String var lastName: String var hashValue: Int { return self.studentID } init(studentID: Int, firstName: String, lastName: String) { self.studentID = studentID self.firstName = firstName self.lastName = lastName } }
At this point, Xcode rightly complains that Type ‘Student’ does not conform to protocol ‘Equatable’. The Equatable
protocol requires us to implement an equality operator for the Student
class. We add the equality operator immediately after the definition of the Student
class.
class Student: Hashable { // Implementation of Student as before ... } func ==(student1: Student, student2: Student) -> Bool { return student1.studentID == student2.studentID }
It is important that the equality operator is defined outside the class Student
in global scope. Swift defines all its equality operators in global scope and looks for custom equality operators there.
The Hashable
protocol requires the equality operator to satisfy the following axiom. If two Student
objects are equal, the hash values of these two objects must be equal as well.
student1 == student2 implies student1.hashValue == student2.hashValue
This is certainly true for our equality operator. This axiom guarantees that there are not multiple entries for the same student in a dictionary. It also guarantees that two Student
objects are different if their hash values are different.
The following unit test adds two students, Larry Palmer and Mike Miller, and their times in the 100-metre dash to the dictionary results
. The unit test “proves” that Mike is faster than Larry.
func testStudentInDictionary() { let larry = Student(studentID: 170523, firstName: "Larry", lastName: "Palmer") let mike = Student(studentID: 251943, firstName: "Mike", lastName: "Miller") var results: [Student: Double] = [:] results[larry] = 10.75 results[mike] = 10.69 XCTAssert(results[mike] < results[larry]) }