In any iOS project, a lot of operations may fail and you have to respond to these errors in your project. Since Swift 2, a new mechanism has been added to the language for responding and dealing with errors in your project. You can now throw and catch errors when you do any operation that may fail for some reason. Suppose, you do some logic to request some data in a JSON format from a remote server and then you save this data in a local database. Can you imagine how many errors may happen for these operations? Connection may fail between your app and the remote server, failing to parse the JSON response, database connection is closed, database file doesn't exist, or another process is writing in database and you have to wait. Recovering from these errors allows you take the appropriate action based on the error type.
Using error handling
Getting ready
Before starting to learn how to handle errors in Swift, you first have to be familiar with how to represent in errors that are going to happen in your program. Swift provides you with a protocol called ErrorType that your errors types should adopt. Then, to represent errors, here comes the role of enumerations to help you. You create a new enum, which lists all error cases, and this enum should conform to the ErrorType protocol. The syntax of using enum with ErrorType will be something like this:
enum DBConnectionError: ErrorType{ case ConnectionClosed case DBNotExist case DBNotWritable }
As we see it's pretty straightforward. You create enum representing the error that conforms to ErrorType protocol, and then list all errors as cases in the enum.
How to do it...
- As usual, let's create a new playground named ErrorHandling.
- Let's create now a new error type for a function that will sign up a new user in a system:
enum SignUpUserError: ErrorType{ case InvalidFirstOrLastName case InvalidEmail case WeakPassword case PasswordsDontMatch }
- Now, create the sign up function that throws errors we made in the previous step, if any:
func signUpNewUserWithFirstName(firstName: String, lastName: String, email: String, password: String, confirmPassword: String) throws{ guard firstName.characters.count> 0 &&lastName.characters.count> 0 else{ throw SignUpUserError.InvalidFirstOrLastName } guard isValidEmail(email) else{ throw SignUpUserError.InvalidEmail } guard password.characters.count> 8 else{ throw SignUpUserError.WeakPassword } guard password == confirmPassword else{ throw SignUpUserError.PasswordsDontMatch } // Saving logic goes here print("Successfully signup user") } func isValidEmail(email:String) ->Bool { let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}" let predicate = NSPredicate(format:"SELF MATCHES %@", emailRegex) return predicate.evaluateWithObject(email) }
- Now, let's see how to use the function and catch errors:
do{ trysignUpNewUserWithFirstName("John", lastName: "Smith", email: "[email protected]", password: "123456789", confirmPassword: "123456789") } catch{ switch error{ case SignUpUserError.InvalidFirstOrLastName: print("Invalid First name or last name") case SignUpUserError.InvalidEmail: print("Email is not correct") case SignUpUserError.WeakPassword: print("Password should be more than 8 characters long") case SignUpUserError.PasswordsDontMatch: print("Passwords don't match") default: print(error) } }
How it works...
We started our code example by creating a new error type called SignUpUserError, which conforms to ErrorType protocol. As we see, we listed four errors that may happen while signing up any user in our system, such as invalid first name or last name, invalid e-mail, weak password, and passwords that don't match. So far, so good!
Then, we create a function signUpNewUserWithFirstName, which takes user input values, and as we can see, we have marked it with the throws keyword. The keyword throws says that this function may throw an error anytime during execution, so you be prepared to catch errors thrown by this method.
Inside the implementation of the function, you will see a list of guard statements that checks for user input; if any of these guard statements returned false, the code of else statement will be called. The statement throw is used to stop execution of this method and throw the appropriate error based on the checking made.
Catching errors is pretty easy; to call a function that throws error, you have to call it inside the do-catch block. After the do statement, use the try keyword and call your function. If any error happens while executing your method, the block of code inside the catch statement will be called with a given parameter called error that represents the error. We've created a switch statement that checks the type of error and prints a user-friendly statement based on the error type.
There's more...
The information that we previously presented is enough for you to deal with error handling, but still there are a couple of things considered important to be known.
Multiple catch statements
In the preceding example, you will notice that we've created a catch statement, and inside, we used a switch statement to cover all cases of error. This is a correct way, but for your reference, we have another way to do this. Consider the following:
catch SignUpUserError.InvalidFirstOrLastName{ } catch SignUpUserError.InvalidEmail{ } catch SignUpUserError.WeakPassword{ } catch SignUpUserError.PasswordsDontMatch{ }
After the do statement, you can list catch statement with the type of error that this statement will catch. Using this method has a condition that the catch statements should be exhaustive, which means it should cover all types of errors.
Disable error propagation
Functions that usually throw an error, in some cases, don't throw an error. In some cases, you may know that calling a function like these with some kind of parameters will never throw an error. In that case, Swift gives you an option to disable error propagation via calling this method with try! instead of try. Calling throwing functions via try! will disable error propagation, and if an error is thrown in that case, you will get a runtime error. So, it's better to take care while using try!.