Chapters

Hide chapters

Design Patterns by Tutorials

Third Edition · iOS 13 · Swift 5 · Xcode 11

13. Iterator Pattern
Written by Jay Strawn

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

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.

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
  }
}
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()
extension Queue: Sequence {
  public func makeIterator()
    -> IndexingIterator<ArraySlice<T?>> {
   
    let nonEmptyValues = array[head ..< array.count]
    return nonEmptyValues.makeIterator()
  }
}
print("List of Tickets in queue:")
for ticket in queue {
  print(ticket?.description ?? "No Description")
}
extension Ticket {
  var sortIndex : Int {
    switch self.priority {
    case .low:
      return 0
    case .medium:
      return 1
    case .high:
      return 2
    }
  }
}
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")
}

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.

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!

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()
  }
}
private var filter = Filter.identity()
self.filter.businesses = businesses
if sender.isOn {
  // 1
  filter = Filter.starRating(atLeast: 4.0)
} else {
  // 2
  filter = Filter.identity()
}
// 3
filter.businesses = businesses

// 4
addAnnotations()
// 1
mapView.removeAnnotations(mapView.annotations)

// 2
for business in filter {

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

Key points

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

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:

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.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now