(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 (
Sequenceprotocol): Defines anything that can be iterated over once. - Collection (
Collectionprotocol): ASequencethat 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
}Sequenceis 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 2let 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, +) // 15let firstEven = numbers.first { $0.isMultiple(of: 2) } // 2Swift 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 modificationfunc 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 createdlet lazyFiltered = numbers.lazy.map { $0 * 2 }.filter { $0 > 5 }
print(Array(lazyFiltered)) // [6, 8, 10]lazyavoids 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
Arraywhen you need fast indexing and modification. - Use
Sequencefor custom iterations and lazy processing. - Optimize performance by avoiding unnecessary copies and using
.lazywhen transforming large collections. - Use
stridefor efficient iteration, chunking, or reverse iteration.