Different error-handling techniques in Swift for both recoverable and non-recoverable errors.

KD Knowledge Diet
2 min readMay 10, 2024

Swift prioritizes compile-time safety to reduce runtime errors. However, failures can still occur. Let’s explore Swift’s error-handling tools, including the differences between preconditionFailure(), assert(), and throwing errors.

Recoverable Errors:

1. Returning nil or an error enum case:

enum DataLoadingError: Error {
case networkError
case invalidData
}
func loadData(from url: URL) -> Data? {
do {
let data = try Data(contentsOf: url)
return data
} catch {
print("Error loading data: \(error)")
return nil
}
}

In this example, the `loadData` function returns `nil` if an error occurs during data loading.

2. Throwing an error:

enum StringFormattingError: Error {
case emptyString
}
func formatString(_ input: String) throws -> String {
guard !input.isEmpty else {
throw StringFormattingError.emptyString
}
return input.uppercased()
}
do {
let formattedString = try formatString("Hello, World!")
print(formattedString)
} catch {
print("Error formatting string: \(error)")
}

Here, the `formatString` function throws an error if the input string is empty. The caller must catch and handle the error.

Non-Recoverable Errors:

1. Using assert() and assertionFailure():

class SomeClass {
var importantValue: Int

init(value: Int) {
assert(value > 0, "Value must be greater than 0")
self.importantValue = value
}

func doSomething() {
assertionFailure("This function should not be called")
}
}
let instance = SomeClass(value: 42)
instance.doSomething() // This line triggers an assertion failure in debug builds.

In this example, the `assert` statement checks that the `importantValue` is greater than 0, and `assertionFailure` indicates that a function should not be called.

2. Using precondition() and preconditionFailure():

func divide(_ dividend: Int, by divisor: Int) -> Int {
precondition(divisor != 0, "Divisor must not be zero")
return dividend / divisor
}
let result = divide(10, by: 0) // This line triggers a precondition failure.

The `precondition` statement checks that the divisor is not zero before performing the division.

3. Calling fatalError():

func performCriticalOperation() {
// Critical operation logic here…
fatalError("Critical error: Operation failed")
}
performCriticalOperation() // This line causes the application to terminate.

`fatalError` is used to indicate a critical, unrecoverable error, causing the application to terminate.

4. Calling exit():

func processCommandLineArguments() {
// Process command-line arguments…

if errorInArguments {
exit(1) // Exit with a non-zero status code indicating an error.
}
}
processCommandLineArguments() // Exits the process if there's an error in arguments.

`exit` is typically used in command-line tools and scripts to exit the process with a specified status code.

These examples illustrate how to handle both recoverable and non-recoverable errors in Swift using various error-handling techniques.

--

--

KD Knowledge Diet

Software Engineer, Mobile Developer living in Seoul. I hate people using difficult words. Why not using simple words? Keep It Simple Stupid!