Dictionaries In Swift Explained

Written by Reinder de Vries on November 27 2018 in App Development

Dictionaries In Swift Explained

A dictionary is a fundamental component in Swift programming. With a dictionary you can store key-value data in your app. It’s a collection type, similar to an array, but completely different too.

In this article you’ll learn:

  • How to create a dictionary, with literal syntax and initializers
  • How to modify dictionaries, by adding, removing and changing elements
  • How arrays and dictionaries are similar, and how they’re not
  • What mutability is, and why it’s relevant
  • Use cases for dictionaries in practical iOS development

Ready? Let’s go.

  1. What’s A Dictionary?
  2. Adding And Removing Dictionary Items
  3. Getting And Changing Dictionary Items
  4. Dictionaries In Practical iOS Development
  5. Further Reading

What’s A Dictionary?

You can use a dictionary to store a collection of key-value information in your app. A dictionary is similar to an array in the sense that both store lists, also called collections, of items of the same type.

Here’s an example of a dictionary:

let scores = [
"Bob": 42,
"Alice": 10,
"Daisy": 33
]

print(scores)

The type of the scores dictionary is [String: Int]. That means that the type of the keys in the dictionary is String, and the type of its values is Int.

In the above example we haven’t explicitly provided the type of scores, because Swift can infer it from the context of our code. Keep in mind though that every value in Swift has a type, even when it’s inferred!

A dictionary associates keys with values. Every key in a dictionary is unique. And dictionary types, just like any other type in Swift, are fixed. You can’t change a value’s type once it has been declared.

Creating a dictionary is straightforward. In the previous example we’ve used shorthand syntax. Like this:

let scores = [
    "Bob": 42,
    "Alice": 10,
    "Daisy": 33
]

The [...] is a dictionary literal. Between the brackets we can put key-value pairs, separated with commas. A key-value pair is separated with a colon. And in the above code, the dictionary is assigned to the scores constant.

Here’s how you create an empty dictionary:

let scores = [String: Int]()
print(scores)

In the above code you’re initializing an empty dictionary of type [String: Int]. You can create an empty dictionary by writing its type followed by parentheses (). Again, the value is assigned to the scores constant.

Let’s talk about mutability for a second. Here’s the gist of it:

  • When a collection, such as a dictionary, is initialized using a constant, with let, that collection is immutable. You cannot change the items in the collection.
  • When a collection, such as a dictionary, is initialized using a variable, with var, that collection is mutable. You can change the items in the collection.

Why the difference? Initializing a collection with let indicates that its value cannot be changed, and that helps us better reason about and understand our code. You can’t accidentally change a collection that wasn’t meant to be changed. Also, properly using let and var also helps the Swift compiler optimize your code.

The syntax for creating and changing arrays and dictionaries is very similar. And just so you know: the dictionary type [String: Int] is shorthand syntax for Dictionary<String, Int>. Dictionaries, like arrays, are generics!

Learn how to build iOS apps

Get started with iOS 12 and Swift 5

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 5 and Xcode 10.

Adding And Removing Dictionary Items

Before we continue, consider the fact that dictionaries are unordered. The key-value items in a dictionary do not have a default order or sorting. The association of key and value pairs is fixed, but their order within the dictionary is undetermined.

Arrays, on the other hand, are ordered. An array starts at index 0, and increments the index by one for every subsequent array element. The index-value pairs are fixed, which means that that arrays have a default order.

Why is that important? For two reasons:

  • When you expect a dictionary to keep it’s order, and it doesn’t, you’re in for a treat (and a lot of bugs and errors).
  • The array, dictionary and set collection type each have different properties and use cases, and it’s helpful to know when to use which type.

Let’s go back to the previous scores example. Here’s the relevant code:

var scores = [
"Bob": 42,
"Alice": 10,
"Daisy": 33
]

print(scores)

Note: The scores dictionary is now mutable, because of the var keyword, as opposed to let for immutable dictionaries.

Adding a new key-valye pair to the scores dictionary is as simple as:

scores["Finn"] = 99

The above code adds a new key "Finn" to the scores dictionary with a value of 99. Note that their types are the same as the scores dictionary, i.e. strings for keys and integers for values.

The scores[...] part is called subscript syntax. It’s super helpful for quickly and succinctly manipulating collections. And you can use it to get and set dictionary items.

Removing an element from a dictionary is done by setting it to nil. Like this:

scores["Alice"] = nil

Unlike arrays, dictionaries do not have an append(_:) function. But… you could code one if you wanted to! Like this:

extension Dictionary
{
    mutating func append(_ value: Value, forKey key: Key)
    {
        self[key] = value
    }
}

This extension adds a function append(_:forKey:) to the Dictionary struct. It also uses the Value and Key generic placeholders instead of actual types, so this function can be used with any kind of key-value types.

Here’s how you would use it:

scores.append(99, forKey: "Finn")
print(scores)
// Output: ["Alice": 10, "Bob": 42, "Finn": 99, "Daisy": 33]

One last thing… What’s interesting for you to try, is that a newly added key-value pair isn’t necessarily added to the “end” of the dictionary. A dictionary is unordered, so it doesn’t have an end! When you add an element to a dictionary, it can be added anywhere in the dictionary. Here, try it:

var scores = [
"Bob": 42,
"Alice": 10,
"Daisy": 33
]

scores["Finn"] = 99

print(scores)

Getting And Changing Dictionary Items

Now that you know subscript syntax, you also know how to get items from a dictionary. Here’s what you can do with subscript syntax:

  • Adding items to a dictionary
  • Removing items from a dictionary
  • Changing an item in a dictionary
  • Getting an item out of a dictionary

Let’s start by getting a value from a dictionary. You can do that by using its key, like this:

let score = scores["Bob"]

The type of score is Int?. That means that the return value of the dictionary subscript is an optional.

Why is it an optional? Because it cannot be certain that a given key exists in the dictionary. When you’re using a key that’s not present in the dictionary, the returned value is nil.

Here’s a comprehensive example:

var scores = [
"Bob": 42,
"Alice": 10,
"Daisy": 33
]

let score_bob = scores["Bob"]
print(score_bob)

let score_unknown = scores["Arthur"]
print(score_unknown)

The value of score_bob is Optional(42), i.e. of type Int?, and the value of score_unknown is nil. Do you see why and how?

As such, optional binding is super useful when used together with getting a value from a dictionary. Like this:

if let score = scores["Bob"] {
    print(score)
}

The type of the score constant is Int, and the above conditional simply isn’t executed when scores doesn’t have a key "Bob". Awesome!

And you’ve guessed it: you change the elements of a dictionary with subscript syntax, too. Here’s how:

let scores = [
"Bob": 42,
"Alice": 10,
"Daisy": 33
]

scores["Alice"] = 101
print(scores)

The value for the key "Alice" has now been changed to 101. So, the syntax for adding, removing, changing and getting items from a dictionary is essentially the same!

But what if you want to know if a key-value pair was newly added to an array, or changed from an existing key-value pair? That’s where the updateValue(_:forKey:) function comes in. Here’s an example:

if let oldScore = scores.updateValue(99, forKey: "Daisy") {
    print("Daisy's old score was: \(oldScore)")
} else {
    print("No player by that name is present...")
}

See what happens there? The updateValue(_:forKey:) function can return two things:

  1. It returns nil when the key wasn’t present in the array before setting it
  2. It returns the present value, i.e. the “old value”, of the key before setting it if the key is present in the array

Because of the updateValue(_:forKey:) function, you know if a key-value pair is newly added or if you’re changing an existing key-value pair. And you can’t know that with ordinary subscript syntax, so that’s a use case for this function.

Dictionaries In Practical iOS Development

Dictionaries are used throughout iOS development, in many forms and shapes. Consider for example the JSON data structure, which is a format widely used by online APIs and webservices.

Here’s an example:

[
    {
        "username": "@reinder42",
        "profile_url": "https://twitter.com/profiles/reinder42.jpg"
        "tweets": [...]
    }, {
        "username": "@arthurdent",
        "profile_url": "https://twitter.com/profiles/arthur_dent.jpg"
        "tweets": [...]
    }, {
        "username": "@xxx_darthvader1999",
        "profile_url": "https://twitter.com/profiles/darthvader.jpg"
        "tweets": [...]
    }
]

If you look closely, you see that the top-level element is an array, denoted by [ ]. And the items in the array are dictionaries, denoted by { }. They associate keys with values, such as "username" and "@reinder42".

When using the above JSON with Swift, you could easily tranform the JSON into a Swift array of dictionaries. Like this:

let twitter = [
    [
        "username": "@reinder42",
        "profile_url": "https://twitter.com/profiles/reinder42.jpg"
        "tweets": [...]
    ], etc.
]

When iterating over the twitter array with a for loop, you could get the name of a Twitter user with:

for item in twitter
{
    let username = item["username"]

    // Do something...
}

And it doesn’t stop there! Dictionaries in Swift are used in…

It’s worth noting here that dictionaries in Swift are based on the principles of a so-called hash table. In short, a hash table can associate hashes with values, in a computer’s memory. Your dictionary keys are translated to hashes with a hash function, with is essentially the same as translating a complex phone number to a small integer value. This integer value is then stored in a lookup table.

When you access a dictionary by its key, the key gets translated using that hash function, and looked up in the table. This is doesn’t require any additional memory per dictionary item, and retrieving an item from the dictionary can be done without going through the entire lookup table one-by-one. This is very efficient and fast.

The problem with hash tables is that two keys can result in the same hash, which is called a hash collision. A good hash table algorithm can deal with collisions in such a way that the time complexity of looking up a value by its key remains O(1). If you’re interested in what makes a dictionary work, it’s well worth it to learn about hash tables.

Learn how to build iOS apps

Get started with iOS 12 and Swift 5

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 5 and Xcode 10.

Further Reading

Dictionaries. Just like arrays, they are fundamental to software development, and building iOS apps. Here’s what you’ve learned in this article:

  • How to create dictionaries, with literal syntax and initializers
  • How to add items to a dictionary, and remove items from it
  • How to get an item from a dictionary, and why the result is an optional
  • How to change dictionary items, and how that affects mutability

Want to learn more? Check out these resources:

Reinder de Vries

Reinder de Vries

Reinder de Vries is a professional iOS developer. He teaches app developers how to build their own apps at LearnAppMaking.com. Since 2009 he has developed a few dozen apps for iOS, worked for global brands and lead development at several startups. When he’s not coding, he enjoys strong espresso and traveling.