Swift Error Handling

Swift error handling is a very important aspect of writing better code. Swift try statement is used for error handling in swift programs. Let’s get started by launching XCode playground.

We come across different kinds of errors in our projects:

The brute force way to handle errors is by using if-else statements where we check each and every possible error. But this can lead to bloated codes with too many nested conditions.

In Swift, Errors are just values of a certain type. Swift does not support checked exceptions.

Swift Error Protocol

Let’s create a basic enum which conforms to this Error Protocol.

enum UserDetailError: Error {
    
    case noValidName
    case noValidAge
    
}

Now let’s use this Error Type in our classes and functions.

throws and throw

If a function or an initializer can throw an error, the throws modifier must be added in the definition itself right after the paratheses and just before the return type.

func userTest() throws -> <ReturnType> {
    
}

The throws keyword would propagate the error from the function to the calling code. Otherwise, a non-throwing function must handle the error inside that function’s code itself.

throw keyword is used for throwing errors from the error type defined. Let’s look at an example demonstrating throws and throw in a function:

func userTest() throws {
    if <condition_matches> {
        //Add your function code here
    }
    else{
        throw UserDetailError.noValidName
    }
}

In Error Handling, guard let is useful in the sense that we can replace the return statement in the else block with the throwing error. This prevents too many if-else conditions.
Let’s look at it with the example below.

func userTest(age: Int, name: String) throws {
    
    guard age > 0 else {
        
        throw UserDetailError.noValidAge
    }
    
    guard name.count > 0 else {
        throw UserDetailError.noValidName
    }
}

Note: You cannot add the Error type after the throws keyword in Swift.

In the above code, if the condition in the guard let fails it’ll throw an error and the function would return there itself.
Let’s look at how to handle these errors.

Swift try, do-catch

do {
    try userTest(age: -1, name: "")
} catch let error {
    print("Error: \(error)")
}

Below image shows the output of the above program. Alternatively, we can do this:

do {
    try userTest(age: -1, name: "")
} catch UserDetailError.noValidName {
    print("The name isn't valid")
} catch UserDetailError.noValidAge {
    print("The age isn't valid")
} catch let error {
    print("Unspecified Error: \(error)")
}

Throwing Errors in Initializers

We can add throws in the initializer in the following way.

import Foundation

enum StudentError: Error {
    
    case invalid(String)
    case tooShort
}

class Student {
    
    var name: String?
    
    init(name: String?) throws {
        guard let name = name else{
            throw StudentError.invalid("Invalid")
        }
        self.name = name
    }
    
    func myName(str: String) throws -> String {
        guard str.count > 5
            else {
                throw StudentError.tooShort
        }
        
        return "My name is \(str)"
    }
}

Now to initialize the class we normally do the following, right?

var s = Student(name: nil) //compiler error

Since the initializer is throwing errors we need to append try keyword as shown below.

do {
    var s = try Student(name: nil)
} catch let error {
    print(error)
}

Swift try, try? and try!

var t1 = try? Student(name: nil)
var t2 = try! Student(name: "BaluTutorial")