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."
]