Skip to content

Instantly share code, notes, and snippets.

@brennanMKE
Created March 8, 2025 06:53
Show Gist options
  • Save brennanMKE/ba764fd1da9e0f88d885c91b0521d3fe to your computer and use it in GitHub Desktop.
Save brennanMKE/ba764fd1da9e0f88d885c91b0521d3fe to your computer and use it in GitHub Desktop.
Swift Arrays, Sequences, and Performance Optimizations

Swift Arrays, Sequences, and Performance Optimizations

(via ChatGPT)

Overview

Swift provides powerful collection types, with Array and Sequence being fundamental. This document explores their differences, best practices, and common pitfalls, including Copy-on-Write (CoW), stride, and Lazy Sequences.


Sequence vs. Collection vs. Array

Swift defines three key protocols for working with ordered data:

  • Sequence (Sequence protocol): Defines anything that can be iterated over once.
  • Collection (Collection protocol): A Sequence that supports multiple passes and indexing.
  • Array (Array<T> type): A concrete, ordered collection that supports fast indexing and iteration.

Sequence Example

struct Countdown: Sequence, IteratorProtocol {
    var count: Int
    
    mutating func next() -> Int? {
        guard count > 0 else { return nil }
        defer { count -= 1 }
        return count
    }
}

for number in Countdown(count: 3) {
    print(number)  // Prints: 3, 2, 1
}
  • Sequence is great for lazy computation but does not support indexing.
  • Iteration occurs once—after consumption, you cannot restart.

Arrays in Swift

Creating and Modifying Arrays

var numbers = [1, 2, 3, 4, 5]
numbers.append(6)      // Adds 6 to the end
numbers.insert(0, at: 0) // Inserts 0 at the start
numbers.remove(at: 2)  // Removes element at index 2

Common Operations

Transforming Arrays

let squared = numbers.map { $0 * $0 }  // [1, 4, 9, 16, 25]

Filtering Arrays

let evens = numbers.filter { $0.isMultiple(of: 2) } // [2, 4]

Reducing Arrays

let sum = numbers.reduce(0, +) // 15

Finding Elements

let firstEven = numbers.first { $0.isMultiple(of: 2) } // 2

Copy-on-Write (CoW) Optimization

Swift collections use Copy-on-Write (CoW) to optimize performance. When a collection is copied, it shares memory until it is modified.

CoW Example

var original = [1, 2, 3]
var copy = original  // No actual copy yet

print(Unmanaged.passUnretained(original as AnyObject).toOpaque())  
print(Unmanaged.passUnretained(copy as AnyObject).toOpaque())  
// Same memory location

copy.append(4)  // Now, a copy is made

print(Unmanaged.passUnretained(original as AnyObject).toOpaque())  
print(Unmanaged.passUnretained(copy as AnyObject).toOpaque())  
// Different memory locations after modification

Avoiding Unnecessary Copies

func modifyArray(_ arr: inout [Int]) {
    arr.append(10)  // No CoW needed
}

var myNumbers = [1, 2, 3]
modifyArray(&myNumbers)

Using stride for Efficient Iteration

stride(from:to:by:) and stride(from:through:by:) generate sequences of numbers efficiently.

Exclusive stride

for number in stride(from: 0, to: 10, by: 2) {
    print(number)  // 0, 2, 4, 6, 8 (excludes 10)
}

Inclusive stride

for number in stride(from: 0, through: 10, by: 2) {
    print(number)  // 0, 2, 4, 6, 8, 10 (includes 10)
}

Reverse stride

for number in stride(from: 10, through: 0, by: -2) {
    print(number)  // 10, 8, 6, 4, 2, 0
}

Lazy Sequences for Performance Optimization

Lazy sequences avoid unnecessary computations, improving performance when chaining operations.

Without Lazy (Creates Intermediate Arrays)

let numbers = [1, 2, 3, 4, 5]
let filtered = numbers.map { $0 * 2 }.filter { $0 > 5 }  
// Multiple arrays are created

Using lazy

let lazyFiltered = numbers.lazy.map { $0 * 2 }.filter { $0 > 5 }
print(Array(lazyFiltered)) // [6, 8, 10]
  • lazy avoids intermediate arrays and computes elements only when accessed.

Using LazySequence with Infinite Sequences

let lazyNumbers = (1...).lazy  // Infinite sequence
let firstFive = lazyNumbers.map { $0 * 2 }.prefix(5)
print(Array(firstFive))  // [2, 4, 6, 8, 10]

Creating a Custom Lazy Sequence

struct Countdown: Sequence, IteratorProtocol {
    var count: Int
    
    mutating func next() -> Int? {
        guard count > 0 else { return nil }
        defer { count -= 1 }
        return count
    }
}

let lazyCountdown = Countdown(count: 5).lazy
for number in lazyCountdown {
    print(number)
}
// Output: 5, 4, 3, 2, 1

Choosing the Right Data Structure

Scenario Use
Need fast random access Array
Iterating once over data Sequence
Avoiding unnecessary copies LazySequence
Infinite sequences Sequence (e.g., AnyIterator)

Summary

  • Use Array when you need fast indexing and modification.
  • Use Sequence for custom iterations and lazy processing.
  • Optimize performance by avoiding unnecessary copies and using .lazy when transforming large collections.
  • Use stride for efficient iteration, chunking, or reverse iteration.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment