UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to loop over arrays

Enumerating, mapping, indices, and more

Paul Hudson       @twostraws

I know what you’re thinking: looping over arrays is trivial, so why should I read an article about it?

Well, it turns out there are several ways of looping over arrays and other collections, and it’s worth recapping them – hopefully you’ll find something new along the way.

First, let’s create a simple collection we can work with: a dictionary containing the names of various science fiction shows, plus a starships from each one:

let ships = [
    "Star Trek": "Enterprise",
    "Firefly": "Serenity",
    "Aliens": "Sulaco"
]

The standard, go-to approach to looping over that dictionary is like this:

for ship in ships {
    print("\(ship.value) is from \(ship.key)")
}

Because dictionaries are collections, just like arrays and sets, you can go over them in reverse:

for ship in ships.reversed() {
    print("\(ship.value) is from \(ship.key)")
}

And you can use enumerated() to get back their position and value in one go:

for (index, ship) in ships.enumerated() {
    print("\(index + 1). \(ship.value) is from \(ship.key)")
}

You can even combine enumerated() and reversed(), but be careful: enumerated().reversed() is not the same as reversed().enumerated():

// this will count down from 3 to 1
for (index, ship) in ships.enumerated().reversed() {
    print("\(index + 1). \(ship.value) is from \(ship.key)")
}

// this will count up from 1 to 3
for (index, ship) in ships.reversed().enumerated() {
    print("\(index + 1). \(ship.value) is from \(ship.key)")
}

Finally, you will occasionally see the forEach() method being used, which does much the same as a regular for-in loop except in a functional style:

ships.forEach {
    print("\($0.value) is from \($0.key)")
}

For forEach() method doesn’t do anything special. In fact, if you take a peek at the Swift source code you’ll see it literally just becomes a for-in loop:

public func forEach(_ body: (Element) throws -> Void) rethrows {
    for element in self {
        try body(element)
    }
}

That doesn’t mean it’s useless, though: using forEach() does guarantee your closure will be run on every item in the collection (there are no surprise break or return statements in there!).

Using forEach() is also nice when you want to call a function with each value. For example, you might write a function to calculate factors, like this:

func calculateFactors(_ number: Int) {
    let factors = (1...number).filter { number % $0 == 0 }
    let stringFactors = factors.map { String($0) }
    let joinedFactors = stringFactors.joined(separator: ", ")
    print("Factors of \(number): \(joinedFactors)")
}

You can then call that directly using forEach():

(1...100).forEach(calculateFactors)
Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

What are you trying to achieve?

Previously I’ve talked about the importance of writing code that conveys your intent, which is why I often recommend map() and compactMap() when transforming array items.

The same lesson applies to looping: when you write a loop, it’s important to consider what you’re actually trying to achieve, because Swift can often provide alternatives that are clearer and perhaps even safer too.

The most common problem people face is looping over array indices, like this:

let movies = [
    "The Phantom Menace",
    "Attack of the Clones",
    "Revenge of the Sith",
    "A New Hope",
    "The Empire Strikes Back",
    "Return of the Jedi"
]

for i in 0 ..< movies.count {
    print("Episode \(i + 1): \(movies[i])")
}

That will print out the numbers and names of the first six Star Wars movies. Of course, any Star Wars fan will know how bad the Phantom Menace was, so they’d probably write this:

for i in 1 ..< movies.count {
    print("Episode \(i + 1): \(movies[i])")
}

And real Star Wars fans would write this:

for i in 3 ..< movies.count {
    print("Episode \(i + 1): \(movies[i])")
}

But both of those code examples contain a possible problem: if your movies array is empty, or lower than your start position, then your code will crash at runtime because creating a range where the start is higher than the end isn’t allowed.

The smarter thing to do is ditch using the index counter entirely by relying on dropFirst() instead. Called with no parameters this will drop the single first item, and called with a parameter will drop that many items.

For example:

for movie in movies.dropFirst() {
    print("Star Wars: \(movie)")
}

for movie in movies.dropFirst(3) {
    print("Star Wars: \(movie)")
}

If you call dropFirst() on an empty array or try to drop more items than you have, it won’t cause a crash – you’ll just get back an empty subsequence.

You can of course combine this with enumerated() or reversed(), like this:

for (index, movie) in movies.dropFirst(3).enumerated() {
    print("Episode \(index + 4): \(movie)")
}

What matters here is that your intent is clear: you want to loop over the array, skipping the first items.

And if you really do want to count from zero up to the size of an array, you should use the indices property instead:

for i in movies.indices {
    print("Episode \(i + 1): \(movies[i])")
}

Compare these two lines of code:

for i in 0 ..< movies.count {
for i in movies.indices {

I know the second option is a little shorter, but it also clarifies your intent – you’re not looping from one arbitrary value to another, but instead going over each index inside the array.

Again, this is so similar to map(). I often quote Javier Soto, who said “map() allows us to express what we want to achieve, rather than how this is implemented.” The same is true of indices: rather than explicitly creating a range by hand, we’re relying on Swift to figure that out.

As with the other approaches, indices can be used with reversed() if you want to loop backwards through a collection:

for i in movies.indices.reversed() {

That kind of code is so much easier to read than the equivalent where you specify the limits by hand:

for i in (0 ..< movies.count).reversed() {
    print(i)
}

As you’ve seen there are lots of ways you can loop over collections. You won’t use them all at the same time, but hopefully you’ve seen how each variant has its own uses.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.