Error Handling For a Safer Code

Error handling is the process of responding to and recovering from error conditions in your program. Swift provides first-class support for throwing, catching, propagating, and manipulating recoverable errors at runtime. Some operations aren’t guaranteed to always complete execution or produce a useful output. Optionals are used to represent the absence of a value, but when an operation fails, it’s often useful to understand what caused the failure, so that your code can respond accordingly.

Before we can really understand how error handling works in Swift, we must see how we would represent an error, In Swift errors are represented by values of types that conform to the Error protocol , Swift’s enumerations are very well suited to modeling error conditions because we have a finite number of error conditions to represent 🙃.

Let’s look at how we would use an enumeration to represent an error :

enum MyError: Error {
 case Minor
 case Bad
 case YouAreFired(description: String)
}

Now let’s see how we can model errors in Swift. For this example , let’s look at how we would assign id number for company employees that can not be greater than 50 or less than 1 and can not be already assigned number (must be unique) so our enumeration would be like :

enum EmployeeNumberError: Error {
 case NumberTooHigh(description: String) 
 case NumberTooLow(description: String)
 case NumberAlreadyAssigned
 case NumberDoesNotExist
}

Now that we know how to represent errors , let’s see how we would throw errors.

When an error occurs in a function , the code that called the function must be made aware of it ; this is called throwing the error. When a functions throws an error, it assumes that the code that called the function, or some code further up the chain , will catch and recover appropriately from the error.

To throw an error from a function , we use the throws keyword. This keyword lets the code that called it know that an error my be thrown from the function.

To demonstrate how to throw errors let’s create an Employee structure :


 typealias Employee = (name: String, id: Int)


  mutating func addEmployee(employee: Employee) throws {
    guard employee.id < maxNumber else {
     throw EmployeeNumberError.NumberTooHigh(description: "Maximum number is \(maxNumber)") 
    } 
    guard  employee.id > minNumber else {
      throw EmployeeNumberError.NumberTooLow(description: "Minimum number is \(maxNumber)") 
    } 
    guard employees[employee.id] == nil else {
     throw  EmplyoyeeNumberError.NumberDoesNotExist
    }
   employees[employee.number] = employee
  }


func getEmployeeByID(id : Int) throws -> Employee {
  if let employee = employees[id] {
    return employee
  } else {
    throw EmplyoyeeNumberError.NumberDoesNotExist
}

in addEmployee function we use the three guard statements to verify that the id number is not too large , not too small , and is unique in the employees dictionary. if the conditions are not met, we throw the appropriate error using the throws keyword. if we make it it through all checks , the employee is added.

Now let’s see how we would catch errors :

When an error is thrown from a function, we need to catch it in the code that called it , this is done using the do-catch block. it looks like this :

do {
  try // (some function that throws)
    // code if no error was thrown
  } catch // pattern here {
  // code if the function threw error
}

also we do not have to include patterns after the catch statement or if we put in an underscore, the catch statement will match all error conditions. so it either will be one of the following :

do {
// our statements
} catch {
 // our error conditions 
}

// or it could be //

do {
 // our statements 
} catch _ {
// our error conditions
}

Now let’s look how we can use the do-catch block in our employee example :

do {
 let employee = try myEmployee.getEmployeeById(id: 12)
  print("Employee is \(employee.name)")
} catch EmployeeNumberError.NumberDoesNotExist{
  print("No employee has that id number")
}

/////////////////////////////////////////////////////////////////

do {
 try myEmployee.addEmployee(employee:(name:"Alex", 25)) 
} catch EmployeeNumberError.NumberTooHigh(let description) {
   print("Error: \(description)")
 } catch EmployeeNumberError.NumberTooLow(let description) {
   print("Error: \(description)")
} catch EmployeeNumberError.NumberAlreadyAssigned {
   print("Error: Number already assigned") 
}

If we are certain that an error will not be thrown, we can call the function using a forced-try expression, which is written as try! . the forced-try expression disables error propagation and wraps the function call in the runtime that no error will be thrown from this call . if an error is thrown, you will get a runtime error that will cause your application to crash ⚠️

and for the results of try? are returned in the form of an optional we would use it with optional binding :

if let employee = try? myEmployees.getEmployeeById(id: 12) {
 print("Employee is \(employee.name)")
}

now let’s talk about cleanup actions , what should we do if we need to perform some cleanup actions regardless of whether or not we had any error ?

simply we use defer statements to execute a block of code just before the code execution leaves the current scope. the following example shows how we can use it :

func deferFunction() {
 print("Function Started")
  var str: String
  defer {
   if let s = str {
     print("str is \(s)")
   }
  }
  str = "Jon"
  print("function finished")
}

the following is the output from this function :

  • Function started

  • Function finished

  • In defer block

  • str in Jon

as you can see the defer block is executed just before we leave the function’s scope .

The defer statement is very useful when we want to make sure we perform all the necessary cleanup , even if an error is thrown . for example : if we successfully open a file to write to , we will always want to make sure we close that file, even if we have an error during the write operation.

That’s it hopefully you got it and it was simple and clear for you, if you found this article helpful please leave some claps and share it so everyone can benefit from it too

If however, you have any concerns or questions, feel free to drop a comment below. You can also drop me a message on twitter

Greetings to SwiftCairo ❤️