Articles, podcasts and news about Swift development, by John Sundell.

Loops

Published on 13 Jan 2022

Swift offers many different built-in ways to iterate over collections (such as arrays, sets, and dictionaries) — the most basic of which being for loops, which let us run a piece of code for each element that was found within a given collection. For example, here we’re looping through an array of names, and we’re then outputting each name by printing it into the console:

let names = ["John", "Emma", "Robert", "Julia"]

for name in names {
    print(name)
}

An alternative way of accomplishing the same thing would be to instead call the forEach method on our array of names, which lets us pass a closure that’ll be run for each element:

names.forEach { name in
    print(name)
}

One key difference between a for loop and forEach, though, is that the latter doesn’t enable us to break the iteration in order to stop it once a given condition has been met. For example, when going back to using a for loop again, we could decide to stop the iteration once the name Robert was encountered:

let names = ["John", "Emma", "Robert", "Julia"]

for name in names {
    if name == "Robert" {
    break
}

    print(name) // Will only print "John" and "Emma"
}

There are also many other ways to use for loops in Swift. For example, if we’d like to gain access to what index that we’re currently handling as part of our iteration, then we could instead choose to base our loop on a range that goes from zero to the number of elements within our collection. We could then use the Array type’s subscripting feature to retrieve the current element for that index — like this:

for index in 0..<names.count {
    print(index, names[index])
}

Another way to write the exact same loop would be to instead iterate over our array’s indicies, rather than constructing a range manually:

for index in names.indices {
    print(index, names[index])
}

Yet another approach would be to use the enumerated method to convert our array into a sequence containing tuples that pair each index with its associated element:

for (index, name) in names.enumerated() {
    print(index, name)
}

Note that the enumerated method always uses Int-based offsets, which in the case of Array is a perfect match, since that collection also uses Int values as its indices.

Next, let’s take a look at while loops, which offer a way for us to repeatedly run a block of code as long as a given boolean condition remains true. For example, here’s how we could use a while loop to keep appending each name within our names array to a string, as long as that string contains less than 8 characters:

let names = ["John", "Emma", "Robert", "Julia"]
var index = 0
var string = ""

while string.count < 8 {
    string.append(names[index])
    index += 1
}

print(string) // "JohnEmma"

Another way to construct a while loop (which perhaps isn’t as commonly used in Swift as in other languages) is by using a separate repeat block, which will also get repeatedly run as long as our while condition evaluates to true:

let names = ["John", "Emma", "Robert", "Julia"]
var index = 0
var string = ""

repeat {
    string.append(names[index])
    index += 1
} while string.count < 8

print(string) // "JohnEmma"

The key difference between repeat and a stand-alone while loop is that a repeat block will always be run at least once, even if the attached while condition initially evaluates to false.

One important thing to keep in mind when using while loops, though, is that it’s up to us to make sure that each loop is ended at an appropriate time — either by manually using break (like we did earlier when using a for loop), or by ensuring that our loop’s boolean condition is met once the iteration should be terminated.

For example, when constructing our name-based string value, we probably want to make sure that the current index won’t go out of the bounds of our names array — since otherwise our app would crash when subscripting into that array. One way to do that would be to attach a second boolean condition to our while statement — like this:

while string.count < 8, index < names.count {
    string.append(names[index])
    index += 1
}

Another approach would be to instead perform the above index-check inline within the loop itself, for example by using a guard statement that’ll break our loop within its else clause:

while string.count < 8 {
    guard index < names.count else {
    break
}

    string.append(names[index])
    index += 1
}

When working with loops, there’s often many different ways to model the same logic, and it can usually be quite useful to prototype a few different approaches in order to figure out which one that’ll work best within each situation. As an example, we could actually have chosen to implement the above iteration using a for loop instead — since we’d be able to attach our string.count-based condition to such a loop by using the where keyword:

let names = ["John", "Emma", "Robert", "Julia"]
var string = ""

for name in names where string.count < 8 {
    string.append(name)
}

print(string) // "JohnEmma"

That doesn’t mean that the above for-based version is objectively better than the while-based one. Picking what type of loop to use is often a matter of taste and code structure, although I’d personally argue that whenever a loop is based on an actual collection of elements, using a for loop is most often the simplest way to go.

Finally, let’s turn our attention to dictionaries, which can also be iterated over using the exact same tools that we’ve been using to loop through arrays and ranges. When iterating over a dictionary, though, we don’t just get access to single elements, but rather (key, value) tuple pairs. For example, here’s how we could iterate through a dictionary that contains a category-based version of our names dataset:

let namesByCategory = [
    "friends": ["John", "Emma"],
    "family": ["Robert", "Julia"]
]

for (category, names) in namesByCategory {
    print(category, names)
}

Sometimes, though, we might not need access to both the keys and values that a given dictionary contains, so it’s also possible to use the _ symbol to ignore a given tuple member within our iteration. Here’s how we could do just that to ignore our dictionary’s values, and only handle its keys (or categories) within our loop:

for (category, _) in namesByCategory {
    print(category)
}

However, while the above is perfectly valid Swift code, there are actually two purpose-built APIs that enable us to perform either a key-only or value-only dictionary iteration simply by accessing either the keys or values property on the dictionary that we’d like to iterate over:

for category in namesByCategory.keys {
    print(category)
}

for names in namesByCategory.values {
    print(names)
}

When working with dictionaries, sets, or other collections that don’t offer a guaranteed element order, it’s important to remember that our loops also won’t be performed in a predictable order. So, for example, if we’d like to ensure that the above category iteration always happens in the same order, then we could make that happen by first sorting our dictionary’s keys into an array — like this:

for category in namesByCategory.keys.sorted() {
    print(category)
}

Our categories will now always be iterated over in alphabetical order. Of course, performing the above kind of sorting operation is only required if the order of elements matters, which will likely only be true for certain iterations.

I hope that you found this Basics article useful, and that you learned at least one new way of constructing a loop in Swift. For more Basics articles, check out this page, and if you have any questions, comments, or feedback (even if it’s positive!), then feel free to reach out via either Twitter or email.

Thanks for reading!

Support Swift by Sundell by checking out this sponsor:

Bitrise

Bitrise: Kick off 2022 by easily setting up fast, rock-solid continuous integration for your project with Bitrise. In just a few minutes, you can set up builds, tests, and automatic App Store and beta deployments for your project, all running in the cloud on every pull request and commit. Try it for free today.