Error handling refers to the process of handling error conditions in your app. Swift 2.0 adds new statements that give you the ability to throw, catch, and manipulate runtime errors. Prior to Swift 2.0, if your function wanted to indicate failure, it would do so by returning an Optional variable with a nil
value. Errors provide a streamlined solution to the problem of indicating failure within a function and handling the failure.
An error can be represented by a class, struct, or enumeration that implements the ErrorType
protocol. In most cases, you will use enumerations to represent errors. The following code snippet lists an enumeration called NetworkError
that could be used to represent error conditions encountered while making a network request.
enum NetworkError: ErrorType {
case ResourceNotFound
case ServerError(httpErrorCode:Int)
case NetworkTimeout
}
NetworkError
could be used to represent three potential scenarios:
ResoureNotFound
: The URL you were trying to reach couldn't be located.NetworkTimeout
: The network request timed out.ServerError
: Any other error generated by the server, the HTTP error code will be included as an associated value—httpErrorCode
.To indicate that a function can throw a runtime error, you must add the throws
keyword to the end of the function declaration:
func doSomething() throws {
…
}
If your function returns a value, then you must add the throws
keyword before the return arrow (->
):
func downloadResource(resourceName:String) throws -> NSData?
{
}
If a function is not declared with the throws
keyword, it cannot throw a runtime error. To throw an error from a throwing function, you can use the throw
keyword:
func downloadResource(resourceName:String) throws -> NSData?
{
if resourceName.isEmpty
{
throw NetworkError.ResourceNotFound
}
return nil;
}
When calling a function that can throw, you must write try
in front of the function call. This keyword is used to indicate the fact that the function being called may throw an error and the lines of code after the function call may not be executed as a result. The following snippet shows how you would call the downloadResource
function.
let homeScreenBanner:NSData? = try downloadResource("homeScreenBanner.png")
Adding a try
statement before a function call does not catch or handle any of the errors that can be generated by the function. It simply serves to highlight the fact that the function you are calling can throw one or more errors. To catch and handle errors, you wrap the call to the function in a do
… catch
statement: The general form of the do
… catch
statement is presented next:
do{
try A function that throws an error
}
catch An Error Matching Pattern {
}
If a function throws an error, that error propagates up the call stack until a suitable catch
clause is found that can handle the error. If no catch
clause is found, then the application is terminated with a runtime error. A catch
clause is followed by an optional pattern used to match errors and a bunch of statements that will be executed if the match is a success.
You can have multiple catch
clauses in a do
… catch
statement. You can also create a catch-all clause by omitting the pattern. Multiple catch
clauses, and a catch-all
clause are demonstrated in the following code snippet:
func loadHomeScreenImages() {
do {
let homeScreenBanner:NSData? = try downloadResource("homeScreenBanner.png")
} catch (NetworkError.NetworkTimeout) {
print("Network error occurred!")
} catch {
print("Some other error occurred!")
}
}
catch
clauses must be exhaustive; you need to either provide a catch
clause for every exception that can be generated by the throwing function or include a catch-all
clause.
You can, however, decide to handle some of the errors and pass the rest higher up the call stack. To do so, simply append the throws
keyword to the method that is handling the partial list of errors. For example, the following code snippet will compile fine even though it does not handle every possible error that the downloadResource
method can throw:
func loadHomeScreenImages() throws {
do{
let homeScreenBanner:NSData? = try downloadResource("homeScreenBanner.png")
}
catch (NetworkError.NetworkTimeout)
{
print("Network error occured!")
}
}
This code will compile because loadHomeScreenImages
itself is declared as a method that can throw errors (in this case, propagate errors from the downloadResource
function).
If you are sure that a throwing function will, in fact, not throw an error at runtime, you can opt to suppress error handling by using the forced try expression (try!
). The following snippet uses the forced try expression in the call to downloadResource
to suppress error handling.
func loadHomeScreenImages() {
let homeScreenBanner:NSData? = try! downloadResource("homeScreenBanner.png")
print("\(homeScreenBanner)")
}
If a method that is called with the forced try expression generates a runtime error, then your application will be terminated.
When a runtime error occurs, code execution usually leaves the current block of code and propagates up the call stack until an appropriate catch
expression is found. Often you may want to execute some cleanup code when an error occurs, before code execution leaves the current scope. The defer
statement allows you to do just that.
A defer
statement delays execution until the current scope is exited:
func downloadResource(resourceName:String) throws -> NSData?
{
if resourceName.isEmpty
{
throw NetworkError.ResourceNotFound
defer {
// insert cleanup code here.
}
}
else
{
return NSData(contentsOfURL: NSURL(string: resourceName)!)
}
return nil;
}
You cannot execute any code in a defer
block that would cause execution control to jump out of the block. Therefore, you cannot use the break
statement or the return
statement, or throw an error.
In this Try It, you create a new Swift playground and build a function that divides two numbers and throws an exception if the denominator is zero.
divideNumbers
that divides two numbers and throws an exception.divideNumbers
and handles any exceptions that are thrown by divideNumbers
.To view the console inside the playground window, select View Debug Area Activate Console.
enum ArithmeticError: ErrorType {
case DivisionByZero
}
Create the divideNumbers
function.
Type the following lines after the definition of the ArithmeticError
enumeration:
func divideNumebrs(numerator n:Double, denominator d:Double) throws -> Double
{
if d == 0
{
throw ArithmeticError.DivisionByZero
}
return n / d
}
Create a function that calls the divideNumbers
function and handles any errors that may be generated.
Type the following lines after the definition of the divideNumbers
enumeration:
func performDivision(number1:Double, _ number2:Double)
{
do{
let result = try divideNumbers(numerator: number1,
denominator: number2)
print("\(number1) divided by \(number2) equals \(result)")
}
catch
{
print ("number2 is zero!")
}
}
performDivision
function.
performDivision
function definition:performDivision(10, 2)
"10.0 divided by 2.0 equals 5.0\n"