Hey there! I’m on the lookout for my next engineering leadership adventure. If you know of any roles let me know through my contact page or on LinkedIn.
You’re already familiar with dictionaries - why else would you be here? They’re
a collection type similar to arrays, but more flexible because their ability to
use a variety of data types to act as the index (or key) value. In many
programming languages, dictionaries can use almost anything as a key - an
integer, a string, a date object, or even an object you’ve created - but in
Swift, you’re limited only to those classes which conform to the Hashable
protocol. If your choice doesn’t conform to that protocol, you’ll run into an
error like the following:
type 'SomeObject' does not conform to protocol 'Hashable'
// ... or ...
Cannot convert the expression's type '[SomeObject: OtherObject].Type' to type 'Hashable'
In both errors above, Swift is complaining that it doesn’t know how to use the specified object as a key. You must provide that information.
In order to use a class as a dictionary key, the first thing we must do is
ensure it conforms to the Hashable protocol. If you’ve done any amount of
iOS or Mac development work, you’re already aware of what protocols are. They’re
a sort of contract whereby classes promise to behave in a certain manner and
function, and that as such, other classes and libraries can trust them.
Let’s create a User class to use as our example. We’ll keep it simple and just
give it an ID (uid) and name properties:
class User {
var uid: Int
var name: String
init(uid: Int, name: String) {
self.uid = uid
self.name = name
}
}
Next we need to tell Swift that our class should conform to the Hashable
protocol. We do so by adding the Hashable protocol after the class
definition.
class User: Hashable {
var uid: Int
var name: String
init(uid: Int, name: String) {
self.uid = uid
self.name = name
}
}
Doing this raises an error stating that “Type ‘User’ does not conform to protocol ‘Hashable’.”
To make User conform to the Hashable protocol, we need to provide a
hashValue getter property. This property must provide a unique identifier for
your objects. If it doesn’t, you’ll wind up retrieving the erroneous data for a
given key. In the case of our User class, we’ll use the uid property as our
unique identifier. We could use name, but there are instance where people
share the same name, for example, John Jacob Jingleheimer Schmidt. Here’s what
our class looks like after making the necessary changes:
class User: Hashable {
var uid: Int
var name: String
var hashValue: Int {
return self.uid
}
init(uid: Int, name: String) {
self.uid = uid
self.name = name
}
}
We’ve declared that our User class conforms to the Hashable protocol, and
defined the hashValue property, everything should work now, right? Not quite.
Because Hashable is derived from (i.e. conforms to) Equatable, our class
must also conform to Equatable, and so the last thing we need to do is define
an version of == which can compare our class. In the case of our User class,
it’s going to look like this:
func ==(lhs: User, rhs: User) -> Bool {
return lhs.uid == rhs.uid
}
The function takes two arguments: the User instance from the left-hand side
of the == (lhs), and the instance from the right-hand side (rhs). It then
returns whether or not the two instances share the same uid value.
The ==(User, User) function must be defined in the global scope, because
this is where Swift defines its own version of the function and where it looks
for any others versions whose signature might be a better match.
When you create your own Equatable or Hashable based classes, you’ll likely
want to define == in the same file as the class to make finding it easier, but
outside of the class definition.
// User.swift
class User: Hashable {
var uid: Int
var name: String
var hashValue: Int {
return self.uid
}
init(uid: Int, name: String) {
self.uid = uid
self.name = name
}
}
func ==(lhs: User, rhs: User) -> Bool {
return lhs.uid == rhs.uid
}
Now that our class conforms to both the Hashable and Equatable protocols,
we can safely use User instances as Dictionary keys.
let user1 = User(uid: 1, name: "Bill")
let user2 = User(uid: 2, name: "Jay")
var user_arrays: [User:String] = [
user1:"The sky above the port was the color of television, tuned to a dead channel.",
user2:"In a hole in the ground there lived a hobbit."
]