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
28: Error Handling with throws and try
I've noticed a recurring pattern when developers first hit error handling in Swift: they treat throw as if it's just a fancy return statement. They think that by marking a function as throws, they're just "tagging" it for later, and that adding try before the call is a polite suggestion to the compiler.
Thinking 'try' is just a label
Here is the mistake. Someone writes a function to process a payment, marks it as throws, and then tries to call it like this:
func processPayment(amount: Double) throws {
if amount <= 0 { throw PaymentError.invalidAmount }
// process payment...
}
// The misconception: "I'll just put 'try' here and it'll be fine."
try processPayment(amount: -5.0)
If you try to compile this in a standard function, the compiler will stop you cold. It'll tell you that try is used in a place where an error isn't handled. The misconception is that try simply "allows" the error to happen. In reality, try is a signal that execution might stop right here. If the function throws, the code immediately below that line will never run. You can't just ignore that possibility; you have to tell Swift exactly what to do when the wheels fall off.
Defining your own failure states
Before you can throw something, you need to define what "something" is. In Swift, we do this using enums that conform to the Error protocol. I always recommend being as specific as possible here. Don't just create a generic AppError; tell me exactly what went wrong.
enum VendingMachineError: Error {
case itemOutOfStock
case insufficientFunds(coinsNeeded: Int)
case invalidSelection
}
Notice the insufficientFunds case. Since enums can have associated values, we can pass the exact amount the user is missing back to the caller. This is way more useful than a boolean false.
The contract of 'throws' and 'do-catch'
When you mark a function with throws, you're signing a contract. You're telling anyone who uses your function, "I might give you a result, or I might blow up in your face."
func vend(item: String, coins: Int) throws {
if item == "Snickers" && coins < 10 {
throw VendingMachineError.insufficientFunds(coinsNeeded: 10 - coins)
}
if item == "Pretzels" {
throw VendingMachineError.itemOutOfStock
}
print("Dispensing \(item)...")
}
To call this, you use a do-catch block. I like to think of the do block as the "happy path"βthe place where you write the code as if everything will work perfectly. The catch blocks are your safety nets.
do {
try vend(item: "Snickers", coins: 5)
print("Enjoy your snack!")
} catch VendingMachineError.insufficientFunds(let needed) {
print("You need \(needed) more coins, buddy.")
} catch {
print("Something else went wrong: \(error)")
}
If vend throws, the print("Enjoy your snack!") line is skipped entirely. The execution jumps straight to the matching catch block. If no specific case matches, the final catch block acts as a catch-all.
The shortcuts: try? and try!
Sometimes, a full do-catch block is overkill. Maybe you don't actually care why it failed, just that it did fail. That's where try? comes in. It converts the result into an optional. If the function throws, it returns nil instead of crashing your program.
let result = try? vend(item: "Pretzels", coins: 100)
// result is now nil because it threw itemOutOfStock
Then there's try!. Use this with extreme caution. It's you telling the compiler, "I am 100% certain this will never fail, and if it does, feel free to crash the entire app." I rarely use this in production, except when loading a bundled resource that I know exists in the app package.
π Practical Task
Build a Robust Password Validator
You need to create a password validation system that doesn't just return true or false, but tells the user exactly why their password was rejected.
Requirements:
- Create an enum called
PasswordErrorthat conforms toError. It should have cases for:tooShort(include an associated value for the minimum length required)missingSpecialCharactercontainsSpaces
- Write a function
validatePassword(_ password: String) throws.- Throw
.tooShortif the password is less than 8 characters. - Throw
.containsSpacesif the password contains any whitespace. - Throw
.missingSpecialCharacterif the password doesn't contain at least one symbol (e.g., !, @, #, $).
- Throw
- Implement a
do-catchblock that tests three different invalid passwords and prints a user-friendly message for each specific error. - Use
try?to attempt to validate a final password and print whether the attempt succeeded or failed using a simple "Success" or "Failure" message.
There are no comments for now.