(via ChatGPT)
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.
Swift defines three key protocols for working with ordered data:
- Sequence (
Sequence
protocol): Defines anything that can be iterated over once. - Collection (
Collection
protocol): ASequence
that supports multiple passes and indexing. - Array (
Array<T>
type): A concrete, ordered collection that supports fast indexing and iteration.
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.
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
let squared = numbers.map { $0 * $0 } // [1, 4, 9, 16, 25]
let evens = numbers.filter { $0.isMultiple(of: 2) } // [2, 4]
let sum = numbers.reduce(0, +) // 15
let firstEven = numbers.first { $0.isMultiple(of: 2) } // 2
Swift collections use Copy-on-Write (CoW) to optimize performance. When a collection is copied, it shares memory until it is modified.
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
func modifyArray(_ arr: inout [Int]) {
arr.append(10) // No CoW needed
}
var myNumbers = [1, 2, 3]
modifyArray(&myNumbers)
stride(from:to:by:)
and stride(from:through:by:)
generate sequences of numbers efficiently.
for number in stride(from: 0, to: 10, by: 2) {
print(number) // 0, 2, 4, 6, 8 (excludes 10)
}
for number in stride(from: 0, through: 10, by: 2) {
print(number) // 0, 2, 4, 6, 8, 10 (includes 10)
}
for number in stride(from: 10, through: 0, by: -2) {
print(number) // 10, 8, 6, 4, 2, 0
}
Lazy sequences avoid unnecessary computations, improving performance when chaining operations.
let numbers = [1, 2, 3, 4, 5]
let filtered = numbers.map { $0 * 2 }.filter { $0 > 5 }
// Multiple arrays are created
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.
let lazyNumbers = (1...).lazy // Infinite sequence
let firstFive = lazyNumbers.map { $0 * 2 }.prefix(5)
print(Array(firstFive)) // [2, 4, 6, 8, 10]
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
Scenario | Use |
---|---|
Need fast random access | Array |
Iterating once over data | Sequence |
Avoiding unnecessary copies | LazySequence |
Infinite sequences | Sequence (e.g., AnyIterator ) |
- 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.