Dictionaries in Swift

A dictionary is a collection of keyed values. Each value in a dictionary must be of the same type, and all the keys must be of the same type, but the keys and values can be of different types (from one another). For example, we could have a dictionary of pirates listed by their UPC1 (an Int) where each value is their favorite saying (a String):

 

pirateUtterances
Key Value
725 “Arrrgh”
92634 “Shiver me timbers”
48763 “heave to an’ prepare to be boarded! ”
467 “Avast, me hearties!”
3648 “Ahoy there, matey”

 

We can find any value in the dictionary by searching for its corresponding key; if we need to know what pirate 467 says, we give the dictionary the key 467, and it responds with “Avast, me hearties!”

Dictionaries, like arrays and sets, are stored as generic collections. The generic constructor for a dictionary type is

Dictionary<KeyType, ValueType>()

so we can declare our pirateUtterances dictionary as:

var pirateUtterances = Dictionary<Int, String>()

or

let pirateUtterances : Dictionary<Int, String>

This gives us an empty dictionary. In the first case, we’re using the generic constructor to get a mutable dictionary, in the second, we’re using the generic type to get an immutable dictionary (that we can follow up with an initialization).

We can then assign key-value pairs by using this syntax:

pirateUtterances = [

725: "Arrrgh",

92634: "Shiver me timbers",

48763: "Heave to an' prepare to be boarded",

476: "Avast, me hearties!",

3648: "Ahoy there, matey"]

that is; [ key : value, key : value, … ].

 

Keys come first in each pair, and the keys and values are separated by colons. Each pair is separated by a comma.

There is a shortcut dictionary type syntax, as there is for arrays. We can declare this dictionary as

let pirateUtterances = [Int : String]()

that is, the type of the key, a colon, and the type of the value, enclosed in square brackets called as a constructor. This is the preferred form of dictionary typing. If used as a type annotation (following a colon), omit the parentheses.

Assuming we have a valid dictionary, we can add or modify elements (key – value pairs) by calling its .updateValue(_: forKey: ) method,2 even if there is no key in the dictionary matching the key given in the method call. For example:

var pirateUtterances = [Int: String]() // empty dictionary of type [Int : String]

pirateUtterances.updateValue("Shiver me timbers", forKey: 92634)

pirateUtterances // [92634 : “Shiver me timbers”]

If we later want to change what pirate # 92634 says, we call the method again:

pirateUtterances.updateValue("Arrrgh", forKey: 92634) // “Shiver me timbers”

pirateUtterances // [92634 : “Arrrgh”]

If you’re paying close attention, you’ll notice that the first time we called .updateValue(…), the return value was nil, and the second time it was “Shiver me timbers.” The .updateValue(_: forKey: ) method returns what was previously stored as the value for that key as an optional, because the value will be nil if its key isn’t already present. We can take advantage of this:

if let oldSaying = pirateUtterances.updateValue("Avast ye!", forKey: 92634) {

print("Pirate 92634 used to say \(oldSaying).")

print("The same pirate now says '\(pirateUtterances[92634]!)'")

}

This will print:

Pirate 92634 used to say Arrrgh.\n

The same pirate now says ‘Avast ye!’\n

As you can see, the syntax to retrieve a value from a dictionary is similar to indexing an array, except we use the key in square brackets, not the array index. The expression pirateUtterances[92634] returns the value for that key, again, as an optional, and again, because it might be nil. We’ve unwrapped it (using the ! explicit unwrapping operator) because 92634 doesn’t say “Optional(“Avast Ye!”)”, now does he? The if – let statement implicitly unwraps oldSaying, so no ! is necessary for this value.

Dictionaries, like Sets and Arrays, have .count and .isEmpty properties. The .count property counts the key – value pairs in the dictionary, not both keys and values.

A key – value pair can be removed from a dictionary either by assigning the value to nil, or using the .removeValueForKey(_:) method:

pirateUtterances[92634] = nil

pirateUtterances.removeValueForKey(92634)

Assigning the value to nil simply removes the key – value pair, but the .removeValueForKey(_:) method returns the old value as an optional.

If we need a collection of all the keys or all the values of a dictionary, we can use the .keys or .values properties. These collections are not arrays (as you might expect), but Swift lazy map collections. Lazy collections are iterable, meaning that they can be iterated over using a loop, which is often very useful. When we discuss loops, we’ll cover how to iterate over collection types.

Sometimes we do need an array containing the keys or values of a dictionary. This is very easy to get; just use the Array constructor, passing in the collection you want to have:

let pirateKeys = [Int](pirateUtterances.keys)

or

let pirateValues = [String](pirateUtterances.values)

In iOS development, you’ll often need an array containing the keys of a dictionary when writing code to deal with table views.

The keys of a dictionary must be hashable, but the values need not be. This means that dictionary keys are most often basic types (Strings are probably most common). If we really need to use complex object types as dictionary keys, those types must adopt the Hashable protocol.

1Unique Pirate Code

2Swift method syntax will be discussed in the next chapter, but for now, note that the parameter list of a method is like a tuple in some ways, especially regarding named elements.

Leave a Comment: