try-catch blocks - Swift 3.0

try-catch blocks - Swift 3.0

Today, I will try to go through some of the error handling techniques Swift uses.

As we all know, the standard way to report error in Objective-C is passing an error pointer such as this


NSError *error = nil;

// Edit - Thanks to @powerje for correction below

// Let's assume you have method like this somewhere
// - (BOOL)checkError:(NSError **)error;

BOOL success = [self checkError:&error];

// The success of operation depends on the value of success flag returned by method. 
// Method may or may not populate the error variable, but if value of success flag is true, 
// it means operation has been successfully completed 

if (error && !success) {
    NSLog(@"Error %@ occurrred", error.localizedDescription);    
}

- (BOOL) performDataFetch:(NSError**)errorPointer {
    if (errorCondition) {
        *errorPointer = [NSError errorWithDomain:... code:... userInfo:...];
    }
}

So we declare an error pointer, pass it to method. If function errors out, it is going to populate errorPointer with the error description and code if applicable.

Sometimes however, as mentioned above if operation results in no error, it will return true value from within the function

Method does not need to return anything since we are simply making changes to error pointer, so that value gets populated automatically. Now, the calling function checks this error pointer and if the value of other than nil (nil value indicates no error), program prints out the error description or it can take it steps further to an additional actions.

Swift, however does not encourage this approach. Error is handled with try-catch mechanism. (More reading can be found at Apple Developer Portal)

Let's try to build a simple project. We have a function that checks for password. If certain conditions are not satisfied, it is going to throw an error.
Below are the conditions it must follow to work smoothly.

  1. Password must be at least 8 characters long
  2. Should contain at least one digit
  3. Should not be commonly used word

If any of these conditions are not met, program will throw an error. Let's look at the implementation


// Enum for password error conditions.
enum PasswordError: Error {
    case TooShort
    case NoNumber
    case CustomMessage(message: String)
}

class PasswordChecker {

    func checkPassword(password: String) throws -> Bool {

        guard password.characters.count >= 8 else {
            throw PasswordError.TooShort
        }
        // Ref: http://stackoverflow.com/questions/29535792/check-if-a-string-contains-at-least-a-uppercase-letter-a-digit-or-a-speical-ch
        let numberRegEx  = ".*[0-9]+.*"
        let texttest1 = NSPredicate(format:"SELF MATCHES %@", numberRegEx)
        guard texttest1.evaluate(with: password) == true else {
            throw PasswordError.NoNumber
        }

        guard password.lowercased() != "password" else {
            throw PasswordError.CustomMessage(message: "Common keyword used as a password")
        }

        return true
    }

    func checkMyPassword(password: String) {
        do {
            let passwordCheckFlag = try self.checkPassword(password: password)
            print(passwordCheckFlag)
            // Do some amazing things with returned password check flag
        } catch PasswordError.TooShort {
            print("Password is too short")
        } catch PasswordError.NoNumber {
            print("Password must contain at least one number")
        } catch PasswordError.CustomMessage(message: let message) {
            print(message)
        } catch {
            print("Unknown Error Occurred while trying to validate password")
        }
    }
}

To summarize, we have a password checker class which validates an input password. If password requirements are not met, it will throw an appropriate error. Errors are defined with enum and there are three cases as below.


enum PasswordError: Error {
    case TooShort
    case NoNumber
    case CustomMessage(message: String)
}

You can verify the internal by making following call


let passwordValidator = PasswordChecker()
// You can pass sample, samplelongenough, password or reallygoodpassword123 to verify the individual cases. 
passwordValidator.checkMyPassword(password: <input_password_field>)

Based on the input thus passed, the program will throw an appropriate error back.

Variations:

Now depending on how you want to use error handling, you may use one of the following two approaches instead of having to individually deal with thrown errors.

  • Converting errors to optional value

In case you do not want to handle an error, you can assume nil return value instead. In the above example you can do something like follows


let validatedPasswordStatus = try? self.checkPassword(password: password)

In the example above, if method checkPassword throws an error, the validatedPasswordStatus will be nil at the end of execution. If not, validatedPasswordStatus will hold whatever Bool values returned by function based on the internal logic.
Thus, in this case using try? keyword, we are suppressing error condition and presence of nil value indicates that call was a failure.

  • Disabling error propagation

In case you're confident enough that error will never be thrown, following operator can be used.


let validatedPasswordStatus = try! self.checkPassword(password: password)

This case assumes that no error will ever be thrown from the checkPassword method. However, in worst case scenario if method throws an error it will cause runtime error and app crash. So please be careful using this approach and whenever possible, employ error handling technique.

Interesting that in this case you almost never want to use try! since the password being passed to method is coming directly from user input which is prone to cause method to throw an error.

However, if you have a error throwing method which outputs the image given the image name and you are certain that given any image name, that image exists in the project and method will always return an image, you can use try! operator here.

However, the output of the method is dependent on the user input, you might as well want to use simple try or try? operator.

GiHub Gist

For More Reading :

Ref: Apple Developer Portal

Special thanks to James Power for showing mistake in the Objective-C code. Thanks buddy!

In the next post we will write some unit tests to verify the code and error conditions it throws based on the input