Make App Pie

Training for Developers and Artists

Thrown Errors

There’s many ways to handle errors in Swift. For some errors, using throws is a great way to handle errors without crashing the system.

Download the exercise file. You’ll find a project with an embedded playground. 

let coffees = ["Sumatra","Colombia","Dark Energy","Sarabanda Dark","Kona"]
let ratings = [2,2,-1,5]

func coffee(_ name:String) -> String{
    let index = coffees.firstIndex{ (coffee) -> Bool in
        coffee == name
    }
    let ratingIndex  = index!
    let rating = ratings[ratingIndex]
    return String(repeating:"☕️", count: rating)
}

var myCoffee = "Colombia"
print(myCoffee + ":" + coffee(myCoffee))

While there’s a lot better ways to do this, I’ll use an example of a function coffee(name:) that finds the rating of a type of coffee using the two arrays coffees and ratings. You’ll notice there’s a nil error and some index out of range errors possible here in the rating and the search. 

I can run for Sumatra as it is set for and it gives me a two cup rating. Try Kona and it crashes since I have four instead of five ratings, so I have an array out fo bounds error. It also crashes on Java, whihc is not one of my coffees, so index is nil and can’t be force unwrapped.

For internal fatal errors of a function, we can use throws. To use throws, add it to the function name before the return type

func coffee(_ name:String) throws -> String{

You’ll need to identify the error when you throw it. You use an enum that adopts the Error protocol to do that. I’ll add three errors 

enum CoffeeError: Error{


I’ll add three errors. The first case will be a coffee not found

case coffeeNotFound

For the second, I can add an argument, which I can later use to give more information about the error in my code

case ratingNotFound(coffee:String)

    The last will be for an invalid rating, since I will only use ratings between one and five cups.

case badRating
}

Going back to my code, I’ll throw those errors in my method. The first error is an nil index, which I will use a guard for.Inside the guard’s else statement I’ll throw the error and send the enumeration’s value, in this case coffee not found. 

//let ratingIndex  = index!
guard let ratingIndex = index else{
    throw CoffeeError.coffeeNotFound
}

For my other two errors, where the rating index is out of range, I’ll use an if statement and throw the appropriate error. For the rating not found, I’ll.include in the parameter the name of the coffee

if ratingIndex >= ratings.count{
   throw CoffeeError.ratingNotFound(coffee: name)
}

If my rating is a nagative one, I’ll throw an error

if rating < 0 {
     throw CoffeeError.badRating
 }

If you make your own thrown errors, that’s how you would set them up. This is great way to detect errors you’ll get from situation beyond your control.

More likely, you will be handling the error. You might set up your own, but there are a lot of factory methods that throw errors. The standard way of handling thrown errors is the do…try…catch construct. It starts with  a do and a code block

do{
print(myCoffee + ":" + coffee(myCoffee))
}

Inside the code block, you try the thrown method, usually before the method. In a print method, it goes outside the print.

do{
    try print(myCoffee + ":" + coffee(myCoffee))
}

   

Add catch to handle any error.  In its simplest form, you can do this to catch and handle all errors. 

do{
    try print(myCoffee + ":" + coffee(myCoffee))
} catch {
    print("Error")
}

The real power of do…try…catch however is in specifying the error form your specified errors, so you can handle it properly.  I’ll set up a specific error handler for a coffee not found. I’ll place this above the gnereal catch. You can think of catch a lot like switch, where the catch without any errors is the default, and should go last.

catch CoffeeError.coffeeNotFound{
    print("Coffee not found")
}

The ratingNotFound error had a parameter, I can set the error parameter with a let in the parameter to silence a warning from string interpolation.

catch CoffeeError.ratingNotFound(let coffee){
    print ("Coffee \(coffee) does not have a rating" )
}

Try a few different cases of errors.  Set the coffee to Dark Energy, run, and you get a simple error from the -1 rating

Change the name to Dark Matter, which isn’t one of our coffees

Try Kona for the out of bounds error:

If yo dont need to hadle errors differently, an alternative is making the error a nil value using try?  and use an if Let construct. 

if let coffeeRating = try? coffee(myCoffee){
    print (myCoffee + " " + coffeeRating )
} else {
    print("Error on coffee")
}

There’s one other option: Disable the error thrown with try!. Use this only if you know this will work, such as 

myCoffee = "Sumatra"
try! print(myCoffee + ":" + coffee(myCoffee))

The best way to handle an error is not to make one. However, when creating methods and classes, you will find places that there are errors you can’t control. Throwing errors will notify any other classes and methods that there is trouble.

The Whole Code

You can find the completed project below, You can also download it from GitHub.

//
//  A Demo for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//
import UIKit

let coffees = ["Sumatra","Colombia","Dark Energy","Sarabanda Dark","Kona"]
let ratings = [2,2,-1,5]

enum CoffeeError: Error{
    case coffeeNotFound
    case ratingNotFound(coffee:String)
    case badRating
}

func coffee(_ name:String) throws -> String{
    let index = coffees.firstIndex{ (coffee) -> Bool in
        coffee == name
    }
    //let ratingIndex  = index!
    guard let ratingIndex = index else{
        throw CoffeeError.coffeeNotFound
    }
    if ratingIndex >= ratings.count{
        throw CoffeeError.ratingNotFound(coffee: name)
    }
    let rating = ratings[ratingIndex]
    if rating < 0 {
        throw CoffeeError.badRating
    }
    return String(repeating:"☕️", count: rating)
}

var myCoffee = "Sumatra"
do{
    try print(myCoffee + ":" + coffee(myCoffee))
} catch CoffeeError.coffeeNotFound{
    print("Coffee not found")
} catch CoffeeError.ratingNotFound(let coffee){
    print ("Coffee \(coffee) does not have a rating" )
} catch {
    print("Error")
}


if let coffeeRating = try? coffee(myCoffee){
    print (myCoffee + " " + coffeeRating )
} else {
    print("Error on coffee")
}

myCoffee = "Sumatra"
try! print(myCoffee + ":" + coffee(myCoffee))

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: