Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
64: The Sequence and IteratorProtocol
One of the most confusing parts of the Swift standard library is the distinction between a Sequence and an IteratorProtocol. On the surface, they both seem to be about "getting a list of things," but they serve fundamentally different purposes. I see developers trip over this constantly, usually when they try to build a custom collection or a data streamer.
struct Countdown: Sequence, IteratorProtocol {
var value: Int
init(start: Int) {
self.value = start
}
mutating func next() -> Int? {
if value <= 0 { return nil }
defer { value -= 1 }
return value
}
}
var timer = Countdown(start: 3)
// First loop: Works great!
for num in timer {
print(num) // Prints 3, 2, 1
}
// Second loop: Does absolutely nothing.
for num in timer {
print(num) // Silence...
}
The One-Shot Iteration Bug
If you run the code above, you'll notice the first for-in loop works exactly as expected. But the second one is a ghost town. Why? Because we made the Countdown struct conform to both Sequence and IteratorProtocol at the same time.
In Swift, an IteratorProtocol is stateful. The next() method mutates the iterator to keep track of where it is in the sequence. By making Countdown its own iterator, we've baked the "current position" state directly into the sequence itself. Once the first loop finishes, value has been decremented to 0. When the second loop starts, it asks the sequence for an iterator, gets itself, and immediately finds that next() returns nil. The sequence is "exhausted."
Separating State from the Sequence
To fix this, we need to separate the definition of the sequence from the process of iterating over it. The Sequence should be a factory that can produce a fresh IteratorProtocol every time it's needed. I usually do this by creating a small, private nested struct for the iterator.
struct Countdown: Sequence {
let start: Int
// This is the "factory" method required by Sequence
func makeIterator() -> CountdownIterator {
return CountdownIterator(start: start)
}
struct CountdownIterator: IteratorProtocol {
var current: Int
init(start: Int) {
self.current = start
}
mutating func next() -> Int? {
if current <= 0 { return nil }
defer { current -= 1 }
return current
}
}
}
let timer = Countdown(start: 3)
for num in timer {
print(num) // Prints 3, 2, 1
}
for num in timer {
print(num) // Now this also prints 3, 2, 1!
}
How the Handshake Works
When you write for num in timer, Swift doesn't actually call next() on the sequence. Instead, it calls makeIterator(). This returns a brand new instance of CountdownIterator. Because the state (the current variable) lives inside that temporary iterator and not in the Countdown struct, we can iterate over the same sequence as many times as we want.
You might wonder: "Why not just make everything a Sequence?" Because some things are meant to be one-time streams. For example, a network socket reading bytes is a perfect candidate for a single-use iterator. But for data structures, you almost always want the factory pattern I showed above. If you find yourself adding mutating to your Sequence methods, stop and ask yourself if you're accidentally treating your sequence like an iterator.
📋 Practical Task
Implementing a Fibonacci Sequence Generator
Your task is to create a custom Sequence that generates Fibonacci numbers. Instead of pre-calculating an array (which would defeat the purpose of a sequence), you must implement the logic within a custom iterator so that numbers are calculated on-the-fly.
Requirements:
- Create a struct named
Fibonaccithat conforms toSequence. - The
Fibonaccistruct should take alimitinteger in its initializer, representing the maximum value the sequence should reach. - Implement a nested
IteratorProtocolthat calculates the next Fibonacci number in the sequence (starting from 0 and 1). - The
next()method should returnnilonce the next Fibonacci number exceeds thelimit. - Verify that you can iterate over the same
Fibonacciinstance twice and get the same results both times.
There are no comments for now.