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
6: Control Flow Statements
One thing I see people struggle with when they first move into control flow is the belief that a switch statement is just a "cleaner" version of an if-else chain. They think, "I'll just use this when I have a lot of conditions to make the code look prettier."
Here is why that mindset will get you into trouble in Swift: in many other languages, if you miss a case in a switch, the program just skips it and moves on. In Swift, the compiler will scream at you. If you're switching over an enum, Swift demands that you handle every single possible case. You can't just "forget" the edge case where the user is offline or the data is corrupted; the language forces you to decide what happens there before the code even runs.
Stop Treating Switch as Optional and Start Embracing Exhaustiveness
I've spent way too many hours debugging "silent failures" in other languages because I forgot one else if. In Swift, the switch is a safety tool. Take a look at this example involving a game state:
enum GameState {
case mainMenu
case playing
case paused
case gameOver
}
let currentState = GameState.playing
switch currentState {
case .mainMenu:
print("Show the start button")
case .playing:
print("Update game physics")
case .paused:
print("Show the pause menu")
// If I forget .gameOver here, the code won't even compile!
case .gameOver:
print("Show the high score screen")
}
The magic here isn't the syntax; it's the fact that if I add a .loading state to my enum later, Swift will highlight every single switch statement in my entire project and tell me exactly where I need to add logic for that new state. That is a massive lifesaver in large projects.
Flattening the "Pyramid of Doom" with Guard
You'll see a lot of beginners writing nested if statements. You know the drill: you check if the user is logged in, then you check if they have a subscription, then you check if the file exists. Suddenly, your code is indented so far to the right that it's practically off the screen. I call this the "Pyramid of Doom."
The fix is the guard statement. Think of guard as a bouncer at a club. Instead of saying "If you're on the list, you can come in (and then I'll check your ID, and then I'll check your age...)", guard says "If you aren't on the list, get out now."
func processPayment(amount: Double?, userIsVerified: Bool) {
guard let actualAmount = amount else {
print("Invalid amount provided.")
return
}
guard userIsVerified else {
print("User needs verification.")
return
}
// Now we are in the "happy path."
// No more nesting!
print("Processing payment of $\(actualAmount)")
}
I always prefer guard for validation. It keeps the "happy path" of your function aligned to the left margin, making the logic way easier to scan at a glance.
Looping Without Losing Your Mind
Swift's for-in loop is the workhorse here. You'll mostly use it to iterate over collections or ranges. But don't forget the while and repeat-while loops for when you don't actually know how many times you need to run the code.
One quick tip: use continue to skip the rest of the current loop iteration and break to kill the loop entirely. I use continue all the time when filtering data manually inside a loop to avoid wrapping the entire loop body in another if statement.
let scores = [85, 42, 91, 33, 78]
for score in scores {
if score < 50 {
continue // Skip the failing scores
}
print("Processing passing score: \(score)")
}
The repeat-while loop is the odd one out—it's basically a while loop that guarantees the code block runs at least once before checking the condition. It's less common, but useful for things like asking a user for input until they provide a valid response.
📋 Practical Task
Build a Smart Home Device Controller
Your task is to create a function that simulates a smart home device controller. You will need to handle different device states and validation checks using the control flow tools we just discussed.
Requirements:
- Create an enum called
DeviceStatuswith the following cases:on,off,standby, anderror. - Write a function called
updateDevice(status: DeviceStatus?, batteryLevel: Int). - Use a
guardstatement to ensure thestatusis not nil. If it is nil, print "No status provided" and exit the function. - Use another
guardstatement to ensure thebatteryLevelis above 5. If it is 5 or lower, print "Battery too low to operate" and exit. - Use a
switchstatement on thestatusto print the following:.on: "Device is fully operational.".off: "Device is shut down.".standby: "Device is in power-saving mode.".error: "Device requires a technician."
Test your code with these scenarios:
updateDevice(status: nil, batteryLevel: 10) // Should trigger first guard
updateDevice(status: .on, batteryLevel: 3) // Should trigger second guard
updateDevice(status: .standby, batteryLevel: 50) // Should print "Device is in power-saving mode."
updateDevice(status: .error, batteryLevel: 20) // Should print "Device requires a technician."There are no comments for now.