Skip to content

Iterator ​

Iterator traverses a collection while hiding how the collection stores its elements. Swift's Sequence and IteratorProtocol already provide the pattern; custom iterators are useful for nonstandard traversal.

When it helps in iOS ​

A photo editor groups selected assets into albums but exports them as one ordered stream. The export loop should not know about the nested arrays.

Original example: flatten album selections ​

swift
import Foundation

struct PhotoSelection: Sequence {
    let albums: [[String]] // Asset identifiers grouped by album.

    func makeIterator() -> AnyIterator<String> {
        var albumIndex = 0
        var assetIndex = 0

        return AnyIterator {
            while albumIndex < albums.count {
                if assetIndex < albums[albumIndex].count {
                    defer { assetIndex += 1 }
                    return albums[albumIndex][assetIndex]
                }
                albumIndex += 1
                assetIndex = 0
            }
            return nil
        }
    }
}

let selection = PhotoSelection(albums: [["asset-A", "asset-B"], [], ["asset-C"]])
for assetID in selection { print(assetID) }

The caller sees one sequence. The iterator skips empty albums and stops when all albums are exhausted.

Production decisions ​

This is a synchronous in-memory iterator. If loading each page or asset requires await, use AsyncSequence instead and propagate cancellation. Make traversal order part of the API contract if exports must be reproducible. For a plain array, use Swift's built-in iterator rather than wrapping it.

Follow the cursor ​

PhotoSelection stores albums as nested arrays but exposes Sequence, so callers see a stream of asset IDs. makeIterator() creates local cursor state: albumIndex selects the album and assetIndex selects an item within it. The closure yields one ID, skips empty albums, and eventually returns nil to signal completion. Each call to makeIterator() gets a fresh cursor, so two loops can traverse the same selection independently.

From example to a photo export ​

The export coordinator can loop over asset IDs and request each image from PhotosUI or PhotoKit. Keeping traversal separate from loading makes ordering explicit and lets the export show progress as processed / total. If loading is asynchronous, use an AsyncSequence or async function that yields results and respects cancellation; the synchronous iterator should not block while fetching images.

Failure modes and verification ​

Test no albums, empty albums between nonempty ones, and multiple independent iterators. Decide whether selection order or album order wins when the user reorders photos. Swift arrays already conform to Sequence; write a custom iterator only when storage or traversal order is genuinely different. Avoid modifying the underlying collection while an iterator is active unless the API defines that behavior.

All patterns

Released under the MIT License.