Swift - Error Handling



Error handling is a process of managing and responding to unexpected situations that occur during the execution of the program. Errors may occur due to various reasons like hardware issues, logical mistakes, unexpected user input, etc.

Swift provides good support for catching, throwing, and manipulating recoverable errors during runtime. The main goals of error handling are:

  • Detect Errors − Identify where and what type of error occurs in the program.

  • Reporting Errors − Report the details of the error to the developer or end-user.

  • Handling Errors − Take proper action against the occurred error which may include providing an error message, logging information for debugging or implementing strategies for recovery.

Now in this article, we will discuss how errors are represented, propogated, and handled by the Swift.

How an error is represented in Swift?

In Swift, an error is represented by using the type that conforms to the Error protocol. The Error protocol is empty, which means it does not have any specific methods and properties to implement instead it allows us to create our own custom error types by defining a new type that conforms to the Error protocol.

The best way to represent errors is enumeration. It grouped all the related error conditions with associated values and information about the error’s behaviour.

Example

The following Swift example will show how to represent an error.

enum MyNewError: Error {
   case invalidInput
   case fileNotFound
   case networkError
   // Add more cases if you require
}

Throwing Error in Swift

In Swift, throwing an error means when a function or a method detects something unexpected that prevents it from executing in the normal way, then it throws an error that tells that something unexpected has occurred. It is further helpful in handling the error. We can throw errors with the help of a throw statement.

Syntax

Following is the syntax for throwing an error −

throw enumerationName.error

Example

Following Swift's example we show how to throw an error.

throw MyNewError.fileNotFound

Propagating Errors Using Throwing Functions

We can propagate errors from the throwing function. The throwing function is a function marked with the throw keyword. In Swift, functions, methods, or initializers can throw errors. When a function, method, or initializer encounters an error, then they can use the throws keyword in their declaration part after the parameter and before the return arrow(->).

Only the throwing function is allowed to propagate errors. If an error occurs inside the non-throwing function, then that error must be handled inside that function.

Syntax

Following is the syntax for the throwing function −

func functionName(parameterList) throws -> ReturnType

Example

The following Swift program will show how to throw an error using the throwing function.

enum MyNewError: Error {
   case invalidInput
   case fileNotFound
   case networkError
}

// Throwing Function 
func processData(_ enterValue: String) throws {
   guard !enterValue.isEmpty else {
      throw MyNewError.invalidInput
   }
}

// Handling errors
do {

   // Calling throwing function
   try processData("")
   print("Successful")
} catch MyNewError.invalidInput {
   print("Invalid input")
} catch {
   print("Unexpected error occurred: \(error)")
}

Output

It will produce the following output −

Invalid input

Disabling Error Propagation

We can disable error propagation with the help of try! Keyword. It is helpful when we are confident the error does not occur at runtime and can simplify the error handling code. However, be careful while using try! Keyword if an error occurs, then our program will crash at runtime.

Example

Swift program to demonstrate how to disable error propagation.

// Set of errors
enum myNewError: Error {
   case divisionByZero
   case invalidInput
}

// Throwing function
func divideNum(_ dividend: Int, by divisor: Int) throws -> Int {
   guard divisor != 0 else {
      throw myNewError.divisionByZero
   }

   return dividend / divisor
}

// Here we're sure that the divisor is not zero, 
// so we use try! to disable error propagation.
let output = try! divideNum(10, by: 2)

print(output)
Output

It will produce the following output −

5

Handling Errors Using Do-Catch

In Swift, we can handle errors with the help of a do-catch statement. It makes our code more robust and allows us to provide specific error-handling logic for various errors. In the do-catch statement, the do block contains the code that might throw an error and the catch block handles the specific error that occurs in the do block.

Not all the errors thrown by the do clause are handled by the catch clause. If none of the catch clauses handle the occurred error, then that error will propagate to the surrounding scope and the surrounding scope will handle the propagated error. If the error propagated to the top-level scope without handling then we will get a runtime error.

In the non-throwing functions, the errors are handled by the enclosed do-catch statement whereas in throwing functions the errors are handled by the enclosed do-catch statement or the caller.

Syntax

Following is the syntax of the do-catch statement −

do
{
   try<expression>
} catch <pattern1>{
   // statement
} catch <pattern2>{
   // statement
} catch <pattern3>, <pattern4> where <condition>{
   // statement
} catch {
   //statement
   // catch clause without pattern can match any error
}

Example

Swift program to demonstrate how to handle errors using do-catch blocks.

// Set of errors
enum myNewError: Error {
   case divisionByZero
   case invalidInput
}

// Throwing function
func divideNum(_ dividend: Int, by divisor: Int) throws -> Int {
   guard divisor != 0 else {
      throw myNewError.divisionByZero
   }
   return dividend / divisor
}

// Handling error using do-catch statement
do {
   let result = try divideNum(10, by: 0)
   print("Result of division: \(result)")
} catch myNewError.divisionByZero {
   print("Found Error: Cannot divide by zero.")
} catch myNewError.invalidInput {
   print("Found Error: Input is not valid.")
} catch {
   print("An unexpected error occurred: \(error)")
}

Output

It will produce the following output −

Found Error: Cannot divide by zero.

Converting Errors to Optional Values in Swift

To convert errors to optional values we can use try?. The try? returns an optional type result. If an error was thrown while evaluating try? expression then the try? expression will return nil, otherwise, it will return the optional that contains the result. This method is useful when we want to handle errors gracefully without any additional information about the error.

Example

Swift program to demonstrate how to convert errors to optional values.

// Set of errors
enum myNewError: Error {
   case divisionByZero
   case invalidInput
}

// Throwing function
func divideNum(_ dividend: Int, by divisor: Int) throws -> Int {
   guard divisor != 0 else {
      throw myNewError.divisionByZero
   }

   return dividend / divisor
}

// Handling error using do-catch statement
do {

   // Using try? to convert errors to optional values
   let output1 = try? divideNum(21, by: 3)
   let output2 = try? divideNum(10, by: 0)
    
   // Checking if the operation was successful using optional binding
   if let res = output1 {
      print("Result of division: \(res)")
   } else {
      print("Found Error")
   }

   if let res = output2 {
      print("Result of division: \(res)")
   } else {
      print("Found Error")
   }
} catch {

   // Handling other errors if available
   print("Found Error: \(error)")
}

Output

It will produce the following output −

Result of division: 7
Found Error
Advertisements