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
20: Enums with Associated Values
I've seen a lot of developers start their network layers like this. It seems intuitive at first: you define an enum for the state and a few optional variables to hold the actual results. Take a look at this snippet:
enum RequestStatus {
case loading
case success
case failure
}
var status: RequestStatus = .loading
var responseData: String?
var errorMessage: String?
// Simulating a successful network call
status = .success
responseData = "Welcome to the dashboard!"
The "Impossible State" Trap
On the surface, this works. But here is where you'll run into trouble as your app grows. Because the status and the responseData are separate variables, they can get out of sync. What happens if a junior dev on your team sets the status to .success but forgets to assign the responseData? Or worse, what if the status is .failure, but responseData still holds the value from a previous successful request?
You end up with "impossible states"—scenarios where your logic says the request failed, but your data variable says it succeeded. You'll find yourself writing defensive code everywhere: if status == .success && responseData != nil { ... }. It's tedious and prone to bugs.
Binding Data Directly to the Case
This is exactly why Swift gives us associated values. Instead of having a separate variable for the data, we can attach that data directly to the enum case itself. This makes the data dependent on the state.
enum RequestStatus {
case loading
case success(String) // The data is now part of the case
case failure(String) // The error message is part of the case
}
// Now, it's physically impossible to have a .success state without the string.
let status = RequestStatus.success("Welcome to the dashboard!")
I love this pattern because it turns a runtime possibility (a null pointer or missing data) into a compile-time guarantee. If you are in the .success state, you must have the associated string. Period.
Extracting Values with Pattern Matching
Since the data is now "wrapped" inside the enum case, you can't just access it with a dot. You have to extract it. The most common way is using a switch statement, which I highly recommend because it forces you to handle every single possibility.
let currentStatus = RequestStatus.failure("404 Not Found")
switch currentStatus {
case .loading:
print("Showing spinner...")
case .success(let message):
print("Success: \(message)")
case .failure(let error):
print("Error: \(error)")
}
Sometimes, you don't care about the other states and just want to do something if the request succeeded. In those cases, don't write a whole switch block. Use if case let. It's a shorthand that keeps your code clean:
if case let .success(message) = currentStatus {
print("Quick win: \(message)")
}
Basically, you're telling Swift: "If the status matches the .success pattern, grab the associated value and call it 'message', then run this block." It's a powerful way to drill down into your data without the boilerplate of a full switch.
📋 Practical Task
Building a Multi-Stage User Authentication State Machine
You are building a login flow. The user goes through several stages, and each stage carries different information. Your goal is to replace a clunky set of optional variables with a single Enum using associated values.
Requirements:
- Create an enum called
AuthStatus. - Define a
loggedOutcase (no associated value). - Define a
verifyingcase that takes anemail(String). - Define a
loggedIncase that takes ausername(String) and auserId(Int). - Define an
errorcase that takes anerrorCode(Int).
The Challenge:
Write a function handleAuthChange(status: AuthStatus) that uses a switch statement to print a specific message for each state. For the loggedIn state, print: "Welcome back [username] (ID: [userId])". For the verifying state, print: "Verification email sent to [email]".
There are no comments for now.