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
67: AsyncSequence and AsyncStream
I remember working with a developer a couple of years ago who was building a real-time stock ticker app. He had implemented a WebSocket listener using the traditional delegate pattern, but his view model had become a sprawling mess of state flags. He had booleans like isConnecting, isListening, and lastReceivedTimestamp, all just to keep track of a stream of data that was arriving at unpredictable intervals. He felt like he was fighting the language to do something that should have been simple: "I just want to loop over the updates as they come in."
That's exactly where AsyncSequence comes in. If a standard Sequence is a collection of items already sitting in memory, an AsyncSequence is a collection of items that arrive over time. Instead of a function returning a whole array at once, it returns a sequence that you can iterate over, but the loop pauses—suspends—every time it waits for the next element to be delivered.
Thinking of Sequences as Timelines
The most powerful part of this is the for await...in loop. You've already used await for single asynchronous returns, but when you combine it with a loop, you're essentially telling Swift: "Run this block of code whenever a new value arrives, and stay suspended in the meantime so you don't block the thread."
// Imagine a sequence that emits a number every second
for await number in tickingClockSequence {
print("The time is now \(number)")
}
// This line isn't reached until the sequence finishes or is cancelled
print("Clock stopped.")
You'll encounter AsyncSequence in the wild quite often. For instance, URLSession.bytes(from:) returns an AsyncBytes sequence, allowing you to process a large file download line-by-line or byte-by-byte without loading the entire file into RAM. It transforms a "big bang" delivery of data into a steady stream.
Bridging Callbacks with AsyncStream
In the real world, you aren't always lucky enough to be using a library that already provides an AsyncSequence. Most of the time, you're dealing with legacy APIs, NotificationCenter, or delegate-based frameworks like CoreLocation. This is where AsyncStream becomes your best friend. It's a concrete implementation of AsyncSequence that lets you "yield" values into a stream from a non-async context.
Think of it as a pipe. You create the pipe, give the "input" end to your delegate or callback, and you hold onto the "output" end as an AsyncSequence. Here is how I usually handle those annoying delegate patterns:
class LocationProvider {
// We wrap the delegate callback in an AsyncStream
var locationUpdates: AsyncStream<CLLocation> {
AsyncStream { continuation in
let manager = CLLocationManager()
let delegate = LocationDelegate { location in
// This "yields" the value to the for-await loop
continuation.yield(location)
}
manager.delegate = delegate
manager.startUpdatingLocation()
// Handle cleanup when the loop is cancelled
continuation.onTermination = { @Sendable _ in
manager.stopUpdatingLocation()
}
}
}
}
I love this pattern because it moves the complex state management out of your business logic and into the stream itself. Your UI code no longer needs to care about CLLocationManagerDelegate; it just iterates over locationUpdates. It turns a "push" based API (the delegate pushing data at you) into a "pull" based API (you awaiting the next value), which is significantly easier to reason about and test.
📋 Practical Task
Build a Real-Time System Log Tailer
Your goal is to create a LogTailer class that simulates reading a log file where new lines are appended at random intervals. Instead of using a timer to poll for changes, you will use an AsyncStream to emit new log entries as they "arrive."
Requirements:
- Create a class
LogTailerwith a propertylogsthat is anAsyncStream<String>. - Inside the
AsyncStreamclosure, use aTaskand awhileloop to simulate a log generator. Usetry await Task.sleep(nanoseconds:)with a random duration (between 0.5 and 2 seconds) before yielding a string like "Log entry at [timestamp]". - Implement a
stop()mechanism using thecontinuation.finish()method to gracefully end the stream. - In your main execution block, use a
for awaitloop to print every log entry to the console as it arrives. - Ensure that you handle the termination of the stream so the program doesn't run infinitely if the task is cancelled.
There are no comments for now.