Chapters

Hide chapters

Design Patterns by Tutorials

Third Edition · iOS 13 · Swift 5 · Xcode 11

13. Iterator Pattern
Written by Jay Strawn

The iterator pattern is a behavioral pattern that provides a standard way to loop through a collection. This pattern involves two types:

  1. The Swift IteratorProtocol defines a type that can be iterated using a for in loop.

  2. The iterator object is the type you want to make iterable. Instead of conforming to IteratorProtocol directly, however, you can conform to Sequence, which itself conforms to IteratorProtocol. By doing so, you’ll get many higher-order functions, including map, filter and more, for free.

What does “for free” mean? It means these useful built-in functions can be used on any object that conforms to Sequence, which can save you from writing your own sorting, splitting and comparing algorithms.

If you’re new to these functions, visit http://bit.ly/sequence-protocol to learn more about them.

When should you use it?

Use the iterator pattern when you have a type that holds onto a group of objects, and you want to make them iterable using a standard for in syntax.

Playground example

Open IntermediateDesignPattern.xcworkspace in the Starter directory, or continue from your own playground workspace from the last chapter, then open the Iterator page.

You’ll be creating a queue in this example.

To quote the Swift Algorithm Club (http://bit.ly/swift-algorithm-club), “A queue is a list where you can only insert new items at the back and remove items from the front. This ensures that the first item you enqueue is also the first item you dequeue. First come, first serve!”

Add the following right after Code Example:

import Foundation

// 1
public struct Queue<T> {
  private var array: [T?] = []

  // 2
  private var head = 0
  
  // 3
  public var isEmpty: Bool {
    return count == 0
  }
  
  // 4
  public var count: Int {
    return array.count - head
  }
  
  // 5
  public mutating func enqueue(_ element: T) {
    array.append(element)
  }
  
  // 6
  public mutating func dequeue() -> T? {
    guard head < array.count,
      let element = array[head] else {
        return nil
    }
    
    array[head] = nil
    head += 1
    
    let percentage = Double(head)/Double(array.count)
    if array.count > 50,
      percentage > 0.25 {
        array.removeFirst(head)
        head = 0
    }
    
    return element
  }
}

Here, you’ve created a queue containing an array. Here’s a breakdown of the code:

  1. You’ve defined that Queue will contain an array of any type.
  2. The head of the queue will be the index of the first element in the array.
  3. There is an isEmpty bool to check if the queue is empty or not.
  4. You’ve given Queue a count.
  5. You have created an enqueue function for adding elements to the queue.
  6. The dequeue function is for removing the first element of the queue. This function’s logic is set up to help keep you from having nil objects in your array.

Next, add the following code to the end of the playground to test the queue:

public struct Ticket {
  var description: String
  var priority: PriorityType

  enum PriorityType {
    case low
    case medium
    case high
  }

  init(description: String, priority: PriorityType) {
    self.description = description
    self.priority = priority
  }
}

var queue = Queue<Ticket>()
queue.enqueue(Ticket(
  description: "Wireframe Tinder for dogs app",
  priority: .low))
queue.enqueue(Ticket(
  description: "Set up 4k monitor for Josh",
  priority: .medium))
queue.enqueue(Ticket(
  description: "There is smoke coming out of my laptop",
  priority: .high))
queue.enqueue(Ticket(
  description: "Put googly eyes on the Roomba",
  priority: .low))
queue.dequeue()

The queue has four items, which becomes three once you’ve successfully dequeued the first ticket.

In a real use-case scenario, you’ll definitely want to be able to sort these tickets by priority. With the way things are now, you’d need to write a sorting function with a lot of if statements. Save yourself some time and instead use one of Swift’s built-in sorting functions.

Currently, if you attempt to use a for in loop or sorted() on queue, you’ll get an error. You need to make your Queue struct conform to the Sequence protocol. Add the following beneath your Queue struct:

extension Queue: Sequence {
  public func makeIterator()
    -> IndexingIterator<ArraySlice<T?>> {
   
    let nonEmptyValues = array[head ..< array.count]
    return nonEmptyValues.makeIterator()
  }
}

Like with dequeue, you want to make sure you’re not exposing nil objects and only iterate through non-empty values.

There are two required parts when conforming the Sequence protocol. The first is your associated type, which is your Iterator. In the code above, IndexingIterator is your associated type, which is the default iterator for any collection that doesn’t declare its own.

The second part is the Iterator protocol, which is the required makeIterator function. It constructs an iterator for your class or struct.

Add the following to the bottom of the file:

print("List of Tickets in queue:")
for ticket in queue {
  print(ticket?.description ?? "No Description")
}

This iterates through your tickets and prints them.

Before you use a sequence-specific sort function, scroll back up and add the following extension underneath the Ticket struct:

extension Ticket {
  var sortIndex : Int {
    switch self.priority {
    case .low:
      return 0
    case .medium:
      return 1
    case .high:
      return 2
    }
  }
}

Assigning numeric values to the priority levels will make sorting easier. Sort the tickets using their sortIndex as reference, add the following code at the end of the file:

let sortedTickets = queue.sorted {
  $0!.sortIndex > ($1?.sortIndex)!
}
var sortedQueue = Queue<Ticket>()

for ticket in sortedTickets {
  sortedQueue.enqueue(ticket!)
}

print("\n")
print("Tickets sorted by priority:")
for ticket in sortedQueue {
  print(ticket?.description ?? "No Description")
}

The sorting function returns a regular array, so to have a sorted queue, you enqueue each array item into a new queue. The ability to sort through groups so easily is a powerful feature, and becomes more valuable as your lists and queues get larger.

What should you be careful about?

There is a protocol named IteratorProtocol, which allows you to customize how your object is iterated. You simply implement a next() method that returns the next object in the iteration. However, you’ll probably never need to conform to IteratorProtocol directly.

Even if you need a custom iterator, it’s almost always better to conform to Sequence and provide custom next() logic, instead of conforming to IteratorProtocol directly.

You can find more information about IteratorProtocol and how it works with Sequence at http://bit.ly/iterator-protocol.

Tutorial project

You’ll continue building onto Coffee Quest from the previous chapter. You’ll finally be adding functionality to the switch in the upper-right corner!

If you skipped the previous chapter, or you want a fresh start, open Finder and navigate to where you downloaded the resources for this chapter. Then, open starter\CoffeeQuest\CoffeeQuest.xcworkspace (not .xcodeproj!) in Xcode.

Note: If you opt to start fresh, then you’ll need to open up APIKeys.swift and add your Yelp API key. See Chapter 10, “Model-View-ViewModel Pattern” for instructions on how to generate this.

Go to the Models group and select File ▸ New ▸ File… and choose Swift File. Name the new file Filter.swift. Create a Filter struct by adding the following code underneath import Foundation:

public struct Filter {
  public let filter: (Business) -> Bool
  public var businesses: [Business]
  
  public static func identity() -> Filter {
    return Filter(filter: { _ in return true }, businesses: [])
  }
  
  public static func starRating(
    atLeast starRating: Double) -> Filter {
      return Filter(filter: { $0.rating >= starRating },
                    businesses: [])
  }
  
  public func filterBusinesses() -> [Business] {
    return businesses.filter (filter)
  }
}

extension Filter: Sequence {
  
  public func makeIterator() -> IndexingIterator<[Business]> {
    return filterBusinesses().makeIterator()
  }
}

This struct holds an array of Business objects and a filter closure. You can instantiate the class with identity(), adjust the filter’s parameters with starRating(), and apply the filter with filterBusinesses().

You also make Filter conform to Sequence via an extentension, wherein you create an iterator from the return value of filterBusinesses().

With your filter wrapper set up, you can now use this logic in the ViewController. Open ViewController.swift. Add the following line of code to the list of properties at the top:

private var filter = Filter.identity()

This creates a new property for filter and sets its default value to Filter.identity().

Next, in the searchForBusinesses function, add the following right underneath the line that reads self.businesses = searchResult.businesses and above the line with DispatchQueue.main.async:

self.filter.businesses = businesses

Here you set filter.businesses to the fetched businesses.

Next, you’ll set the filter based on the top-right switch. Add the following code inside businessFilterToggleChanged(_:):

if sender.isOn {
  // 1
  filter = Filter.starRating(atLeast: 4.0)
} else {
  // 2
  filter = Filter.identity()
}
// 3
filter.businesses = businesses

// 4
addAnnotations()

Here’s how this works line by line:

  1. If the switch is on, you set filter to Filter.starRating(atLeast: 4.0). This will only show coffee shops that are rated 4.0 or better.

  2. If the switch isn’t on, you set filter to Filter.identity(). This will show all coffee shops.

  3. In either case, you set filter.businesses to the existing businesses that were previously fetched.

  4. Lastly, you call addAnnotations() to update the map.

You also need to actually use the filter whenever you update the map. Replace the contents of addAnnotations() with the following:

// 1
mapView.removeAnnotations(mapView.annotations)

// 2
for business in filter {

  // 3
  let viewModel =
    annotationFactory.createBusinessMapViewModel(for: business)    
  mapView.addAnnotation(viewModel)
}

Here’s how this works:

  1. You first remove the existing annotations from the map. This prevents duplicates from being shown, which is possible because this method is called whenever the user toggles the switch.

  2. You loop through each business in filter. Under the hood, this calls makeIterator() on Filter, which calls filterBusinesses().makeIterator, and this is what actually filters the businesses.

  3. You create a viewModel for each business and add this to the map.

Build and run the app, and try toggling the switch in the top-right corner a few times. When it’s turned on, only highly-rated coffee shops will be shown. When it’s off, all of the coffee shops will be shown.

Key points

You learned about the iterator pattern in this chapter. Here are its key points:

  • The iterator pattern provides a standard way to loop through a collection using a for in syntax.

  • It’s better to make your custom objects conform to Sequence, instead of IteratorProtocol directly.

  • By conforming to Sequence, you will get higher-order functions like map and filter for free.

Where to go from here?

You’ve added a lot of great functionality to Coffee Question over the last few chapters! However, there’s still many more features you could add:

  • Advanced filtering and searching options
  • Custom address input instead of just searching nearby
  • Saving and displaying favorited coffee shops

Each of these are possible using the existing patterns you’ve learned so far. Feel free to continue building out Coffee Quest as much as you like.

When you’re ready, continue onto the next chapter to learn about the prototype design pattern and build a new example app.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.