Lesson 14 - Handling errors in Swift

Swift OOP Handling errors in Swift

In the previous lesson, Overloading operators and other useful syntax in Swift, we talked about operator overloading and did a few more tricks in Swift. In today's Swift tutorial, we're going to explain the last important topic before we move to form applications. The topic is errors. Since we'll stumble across errors in form applications a lot, we're going to learn how to handle them today.

Errors

In programming, errors occur quite frequently as you might have noticed :) Some are our fault, some are just going to occur no matter what. A typical example is reading data from a file or web service which is not available at the moment. The reasons might be another programmer's mistake, no Internet connection and so on.

In this tutorial, we'll show how to handle those errors at runtime, so the program won't crash.

Swift, just like other modern languages, use a concept of exceptions. An exception refers to the error state of the application. If you come from another language, it might be very familiar to you. However, the "exception" term isn't used here and several things work a little differently.

Running dangerous code

First, let's have a look at how to handle a code that can possibly cause an error. Swift is more strict in this area and functions that can cause an error have to be marked with the throws keyword written before the return type. A similar rule exists e.g. in Java. Then, we can't work with the function as we are used to.

Let's have a look at a sample code writing to a file. Create a new Command Line Tool project and name it ErrorFile. Put the following code in the main.swift file:

// This code doesn't work yet
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as NSURL
let fileUrl = documentsUrl.appendingPathComponent("info.txt")!
"ICT.social is awesome!".write(to: fileUrl, atomically: true, encoding: String.Encoding.unicode)

The code above maybe looks scary, but it's nothing complex. Let's describe it.

  • The first line retrieves the path to the Documents folder from the system.
  • The second line appends the filename, which we're going to write to, to the path.
  • The third line uses a String method to write to a file. The rest of the parameters deal with a file lock and encoding.

But this code won't work just yet. It's because we didn't handle the error that might occur when we fail to write to the file. The error mechanism in Swift is fairly similar to the Optional mechanism since we have to deal with all unexpected states. The write() method is marked with the throws keyword. It means that as soon as we use it we have to handle all possible error states.

Try

We can solve the problem using the try keyword which we write at the beginning of the third line. But not just that alone.

The simplest approach is to write try? which is again very similar to the Optional concept. If something fails, nothing will simply happen and the program continues. If we used try? together with a method which returns a value, we'll get an Optional which is empty in the case of an error.

We can also use try!. Again, the exclamation mark represents a dangerous situation here, and it practically tells Swift that no error can happen and that's why we don't want to deal with possible error states. If an error occurs anyway, the program crashes.

A do-catch block

The most common approach is probably wrapping try into the do-catch block. See the notation below:

do
{
    try "ICT.social is awesome!".write(to: fileUrl, atomically: true, encoding: String.Encoding.unicode)
}
catch {
    print("Failed to write to the file")
}

The do block allows us to use the try keyword alone. If an error occurs, the catch block is executed. There we can access the error variable and retrieve the particular error if something went wrong. We can also get some additional information from the error. In further lessons, we'll show how to react to different error types using multiple catch blocks. After executing the code above, you can see a new text file in your Documents folder to which we wrote some text.

A defer block

The defer block executes its code after the block with try has finished. That means, in case the try was successful, but also in case the catch block was executed etc.

Before we look at how to use the defer block, let's put the previous code into a method. We can leave it in the main.swift file. Such a method is actually called a function because a method always belongs to a class.

func writeToFile(text: String) {

    let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as NSURL
    let fileUrl = documentsUrl.appendingPathComponent("info.txt")!

    do {
        try text.write(to: fileUrl, atomically: true, encoding: String.Encoding.unicode)
    }
    catch {
        print("Failed to write to the file, because: \(error)")
    }
}

In order to make the example as simple as possible, let's add a basic logging after our method is called. We'll add a defer block to the beginning:

func writeToFile(text: String) {
    defer {
        print("The writeToFile() method was executed")
    }

    let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as NSURL
    let fileUrl = documentsUrl.appendingPathComponent("info.txt")!

    do
    {
        try text.write(to: fileUrl, atomically: true, encoding: String.Encoding.unicode)
    }
    catch {
        print("Failed to write to the file, because: \(error)")
    }

    // here the defer block is processed
}

Even though the defer block is declared first, it's executed not before the program leaves the method's scope (the braces block) in which the defer is used. In our case, when the program exits the method. This block is always executed, even if the method finishes or is left because it returned something by return. We could use it in e.g. a loop as well. Then it's executed when the loop is interrupted by break or when it ends naturally. Basically, there's no way the code isn't executed. That's why it's called defer, we put there the code to be executed later. Typically, a clean up is performed there, for example freeing memory and such actions which have to be done regardless of whether the dangerous operation went successfully or not. We'll see this mechanism again in further courses.

Now we know how to use functions that can cause errors. Even though we'll handle errors from library functions more often, we'll learn how to raise our own errors too. That's the next lesson's topic, Enums and custom errors in Swift.


 

 

Article has been written for you by Filip Němeček
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
Activities (2)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!