Lesson 83Error Handling
Do-Try-Catch
Handling Thrown Errors
When calling a throwing function, you must handle potential errors using do-try-catch. This is Swift's primary error handling mechanism.
Basic Structure
do {
let result = try throwingFunction()
} catch {
print(error)
}
let result = try throwingFunction()
} catch {
print(error)
}
Catch Patterns
Catch All
catch { ... }Catches any error
Specific Error
catch MyError.case { ... }Catches one case
With Pattern
catch let e as MyError { ... }Cast to specific type
With Where
catch Error.case(let x) where x > 0Conditional catch
The error Variable
In a catch block without a pattern, Swift automatically provides an error constant of type Error.
Order Matters!
Put specific catches before general ones! Swift matches from top to bottom, and the first matching catch wins.
- Most specific patterns first
- General
catchblock last - Unreachable catches cause warnings
Multiple try in One Block
You can have multiple try calls in one do block. If any fails, execution jumps to catch immediately.
main.swift
// DO-TRY-CATCH
// Handle errors from throwing functions
// BASIC SYNTAX
enum MathError: Error {
case divisionByZero
case negativeRoot
}
func divide(_ a: Int, by b: Int) throws -> Int {
if b == 0 { throw MathError.divisionByZero }
return a / b
}
func squareRoot(_ n: Double) throws -> Double {
if n < 0 { throw MathError.negativeRoot }
return n.squareRoot()
}
// BASIC DO-TRY-CATCH
do {
let result = try divide(10, by: 2)
print("Result: \(result)") // Result: 5
} catch {
print("Error: \(error)")
}
// CATCHING SPECIFIC ERRORS
do {
let result = try divide(10, by: 0)
print(result)
} catch MathError.divisionByZero {
print("Cannot divide by zero!")
} catch MathError.negativeRoot {
print("Cannot take square root of negative!")
} catch {
print("Unknown error: \(error)")
}
// MULTIPLE TRY STATEMENTS
do {
let a = try divide(100, by: 5)
let b = try divide(a, by: 2)
let c = try squareRoot(Double(b))
print("Final result: \(c)")
} catch {
print("Operation failed: \(error)")
}
// CATCH WITH PATTERN MATCHING
enum NetworkError: Error {
case timeout(seconds: Int)
case serverError(code: Int)
case noConnection
}
func fetchData() throws -> String {
throw NetworkError.serverError(code: 500)
}
do {
let data = try fetchData()
print(data)
} catch NetworkError.timeout(let seconds) {
print("Request timed out after \(seconds) seconds")
} catch NetworkError.serverError(let code) where code >= 500 {
print("Server error: \(code)")
} catch NetworkError.serverError(let code) where code >= 400 {
print("Client error: \(code)")
} catch NetworkError.noConnection {
print("No internet connection")
} catch {
print("Other error: \(error)")
}
// CATCH MULTIPLE ERROR TYPES
enum FileError: Error {
case notFound
case permissionDenied
}
do {
// Some operation
} catch is NetworkError {
print("Network-related error")
} catch is FileError {
print("File-related error")
} catch {
print("Other error")
}
// NESTED DO-CATCH
func processData() {
do {
let data = try fetchData()
do {
// Process the data
let processed = try transform(data)
print(processed)
} catch {
print("Transform failed: \(error)")
}
} catch {
print("Fetch failed: \(error)")
}
}
func transform(_ data: String) throws -> String {
return data.uppercased()
}
// PRACTICAL EXAMPLE: API Response
enum APIError: Error {
case invalidResponse
case decodingFailed
case unauthorized
}
struct User: Codable {
let id: Int
let name: String
}
func parseUserResponse(json: Data) throws -> User {
let decoder = JSONDecoder()
do {
return try decoder.decode(User.self, from: json)
} catch DecodingError.keyNotFound(let key, _) {
print("Missing key: \(key.stringValue)")
throw APIError.decodingFailed
} catch DecodingError.typeMismatch(let type, _) {
print("Type mismatch for: \(type)")
throw APIError.decodingFailed
} catch {
throw APIError.invalidResponse
}
}Try It Yourself!
Write a function that parses a date string and use do-try-catch to handle different parsing errors with specific error messages!