As we mentioned earlier, error handling, in its most simplest form, uses the return value from a function or method to indicate whether it was successful or not. This return value could be something as simple as a Boolean true/false value or something more complex such as an enumeration, whose values indicate what actually went wrong if the function or method was unsuccessful. This was one of the primary error-handling patterns with Objective-C and also with Swift prior to version 2.0. This is still a very valid pattern if we just need something to indicate success or failure.
Let's see how we would use the return value to add error handling to the drinking()
method of the Drink
structure. For this function, we just need an indicator that will let us know if we were able to successfully take the drink. This is the ideal situation for using a Boolean return value. A return value of true
will indicate that the function was successful and a return value of false
will indicate that it failed. Let's see how we would implement this with the drinking()
method:
mutating func drinking(amount: Double) -> Bool { guard amount <= volume else { return false } volume -= amount return true }
In this method, we use the guard
statement to verify that the amount that we want to drink is less than or equal to the volume remaining in the drink. If not, we return a false
value indicating that the method failed; otherwise, we subtract the amount we drank from the volume remaining in the drink and return a true
value, indicating that the method was successful.
Adding a simple Boolean return value to our methods is one of the easiest ways to add error handling to our applications. It is also very easy to check for errors with this type of error handling. For example the following code creates an instance of the Drink
class and then uses the drinking()
method to take a drink. We then verify that we successfully took a drink:
var myDrink = Drink(volume: 23.5, caffeine: 280, temperature: 38.2, drinkSize: DrinkSize.Can24, description: "Drink Structure") if myDrink.drinking(50.0) { print("Had a drink") } else { print("Error") }
In this code, to check for errors all we needed to do was to call to the drinking()
method in an if
statement to verify that the method was successful.
Now let's look at how we would use a return value to indicate whether the temperature of the drink is still in the acceptable range after a call to the temperatureChange()
method. The return value for the method will be an enumeration that will let us know if the temperature of the drink is too cold, too hot, or just right. This enumeration will look like this:
enum DrinkTemperature { case TooHot case TooCold case JustRight }
Now let's see how we would change the temperatureChange()
method to let us know whether the temperature of the drink is within the acceptable range:
mutating func temperatureChange(change: Double) -> DrinkTemperature { temperature += change guard temperature >= 35 else { return .TooCold } guard temperature <= 45 else { return .TooHot } return .JustRight }
The acceptable range of temperature for our drinks is between 35
and 45
degrees; therefore in our code we need to verify that the temperature of the drink is greater than or equal to 35
degrees and less than or equal to 45
degrees. In our new method we use a guard
statement to verify that the drink is greater than or equal to 35
degrees; otherwise we return a TooCold
value. We then use another guard
statement to verify that the drink is less than or equal to 45
degrees; otherwise we return a TooHot
value. If the temperature successfully passes the two guard statements, we return a JustRight
value.
We would use this function like this:
var results = myDrink.temperatureChange(-5) switch results { case .TooHot: print("Drink too hot") case .TooCold: print("Drink too cold") case .JustRight: print("Drink just right") }
In this example we used a switch
statement after we changed the temperature to see if the temperature of the drink got too hot, too cold, or was still in the acceptable range. We could use an if
statement if all we wanted to do was to verify that the temperature was in an acceptable range.
If we needed additional information about our error, we could use associated values with our enumeration. In our example, this additional information could be the actual temperature of the drink.
What we saw in this section is the easiest error-handling pattern to implement; however, there are several drawbacks to this pattern. The biggest drawback to this error-handling pattern is that it is by far the easiest form to ignore. It is very easy, in our code, to call these functions and then ignore the returned value. This can cause issues in our application if we simply assume a function/method was successful when it isn't.
As a general rule, if a function/method has a return value we should always check it to verify that the function completed successfully. The next error handling pattern that we will look at is how we would use the NSError
class to return detailed information about an error.