Lesson 82Error Handling
Throwing Functions
The throws Keyword
Functions that can fail are marked with throws in their signature. This tells the compiler and other developers that this function might throw an error.
Syntax
func doSomething() throws -> ReturnType
Where throws Can Be Used
Functions
Regular throwing functions
Initializers
Failable init with throw
Closures
Throwing closure types
Subscripts
Swift 5.5+ feature
throws vs rethrows
throws
Function always requires try when called
rethrows
Only throws if the closure parameter throws
Common Pattern
Use guard statements with throw for validation:
guard condition else { throw MyError.invalidInput } Async + Throws
For async functions that can fail:
func fetch() async throws -> Data Note: async comes before throws
main.swift
// THROWING FUNCTIONS
// Functions that can fail are marked with 'throws'
// BASIC THROWING FUNCTION
enum DivisionError: Error {
case divisionByZero
}
func divide(_ a: Int, by b: Int) throws -> Int {
if b == 0 {
throw DivisionError.divisionByZero
}
return a / b
}
// THROWING WITH MULTIPLE ERROR CASES
enum WithdrawalError: Error {
case insufficientFunds(available: Double)
case accountLocked
case dailyLimitExceeded(limit: Double)
}
class BankAccount {
var balance: Double = 1000
var isLocked: Bool = false
let dailyLimit: Double = 500
func withdraw(amount: Double) throws -> Double {
guard !isLocked else {
throw WithdrawalError.accountLocked
}
guard amount <= dailyLimit else {
throw WithdrawalError.dailyLimitExceeded(limit: dailyLimit)
}
guard amount <= balance else {
throw WithdrawalError.insufficientFunds(available: balance)
}
balance -= amount
return amount
}
}
// THROWING INITIALIZERS
struct User {
let email: String
let password: String
enum ValidationError: Error {
case invalidEmail
case passwordTooShort
}
init(email: String, password: String) throws {
guard email.contains("@") else {
throw ValidationError.invalidEmail
}
guard password.count >= 8 else {
throw ValidationError.passwordTooShort
}
self.email = email
self.password = password
}
}
// THROWING CLOSURES
let riskyOperation: () throws -> Int = {
let random = Int.random(in: 1...10)
if random < 5 {
throw NSError(domain: "RandomError", code: random)
}
return random
}
// FUNCTION THAT ACCEPTS THROWING CLOSURE
func performOperation(_ operation: () throws -> Int) throws -> Int {
print("Starting operation...")
let result = try operation()
print("Operation completed!")
return result
}
// RETHROWS - Function throws only if its closure throws
func transform<T>(_ items: [T], using closure: (T) throws -> T) rethrows -> [T] {
var result: [T] = []
for item in items {
result.append(try closure(item))
}
return result
}
// Non-throwing closure - no try needed
let doubled = transform([1, 2, 3]) { $0 * 2 }
print(doubled) // [2, 4, 6]
// Throwing closure - try needed
enum TransformError: Error { case negative }
let numbers = try transform([1, 2, -3]) { n in
if n < 0 { throw TransformError.negative }
return n * 2
}
// THROWING COMPUTED PROPERTIES (Swift 5.5+)
struct Config {
private var data: [String: Any] = [:]
enum ConfigError: Error {
case missingKey(String)
}
subscript(key: String) -> Any {
get throws {
guard let value = data[key] else {
throw ConfigError.missingKey(key)
}
return value
}
}
}
// ASYNC THROWING FUNCTIONS
func fetchUser(id: Int) async throws -> String {
// Simulating network call
if id < 0 {
throw NetworkError.invalidID
}
return "User \(id)"
}
enum NetworkError: Error {
case invalidID
}Try It Yourself!
Create a FileManager class with a throwing readFile(name:) method that throws different errors for missing files, permission denied, and corrupted files!