Error handling allows you to write robust code that can handle unexpected situations gracefully and prevent your program from crashing. Swift supports error handling with the try
, catch
, do
, throw
, and throws
keywords.
There’s no way to specify the type of error that throws out because Swift’s error handling system is designed to be flexible and expressive. Any type that conforms to the Error
protocol can be used to represent an error condition, and the compiler does not enforce any specific error type for a throwing function. This allows you to use different kinds of errors for different situations, such as enums, structs, classes.
However, this also means that you cannot rely on the compiler to check the error type for you, you have to use runtime checks or type casting to determine what kind of error you are dealing with.
When you use do...catch
in Swift to handle errors, you can write a pattern after catch to indicate what errors that clause can handle. A pattern is a way of describing a value or a range of values that can match a certain condition.
do {
try <#expression#>
<#statements#>
} catch <#pattern 1#> {
<#statements#>
} catch <#pattern 2#> where <#condition#> {
<#statements#>
} catch <#pattern 3#>, <#pattern 4#> where <#condition#> {
<#statements#>
} catch {
<#statements#>
}
If a catch clause doesn’t have a pattern, the clause matches any error and binds the error to a local constant named error
. This means that you can access the error object in the catch block without specifying its type or name.
There are different ways to catch different errors in Swift, depending on the type and structure of the error:
enum NetworkError: Error {
case invalidURL
case noConnection
case timeout
}
func downloadData(from url: String) throws -> Data {
// some code that might throw a NetworkError
}
do {
let data = try downloadData(from: "https://example.com")
// use data
} catch NetworkError.invalidURL {
// handle invalid URL error
} catch NetworkError.noConnection {
// handle no connection error
} catch NetworkError.timeout {
// handle timeout error
}
enum FileError: Error {
case notFound(path: String)
case permissionDenied(user: String)
case outOfSpace(available: Int)
}
func readFile(at path: String) throws -> String {
// some code that might throw a FileError
}
do {
let contents = try readFile(at: "/Users/bob/Documents/file.txt")
// use contents
} catch FileError.notFound(let path) {
// handle not found error with path
} catch FileError.permissionDenied(let user) {
// handle permission denied error with user
} catch FileError.outOfSpace(let available) {
// handle out of space error with available bytes
}
enum MathError: Error {
case divisionByZero
case overflow
case underflow
}
func divide(_ x: Int, by y: Int) throws -> Int {
// some code that might throw a MathError
}
do {
let result = try divide(10, by: 0)
// use result
} catch is MathError {
// handle any math error
}
enum DatabaseError: Error {
case connectionFailed(url: String)
case queryFailed(errorCode: Int)
}
func executeQuery(_ query: String) throws -> [String: Any] {
// some code that might throw a DatabaseError
}
do {
let result = try executeQuery("SELECT * FROM users")
// use result
} catch let error as DatabaseError {
// handle any database error and access the error value
}