Wisdom Ekpot A student of Ibom Metropolitan Polytechnic studying computer engineering, Wisdom has been writing JavaScript for two years, focusing on Vue.js, Angular, and Express.js.

Error handling in Swift

4 min read 1312

Error handling is essential for the success of every application, ensuring end users can easily navigate through the app and use the features correctly.

In this tutorial, we’ll learn how to detect and resolve errors in a simple bookstore application built using Swift, the general-purpose programming language developed by Apple and the open source community.

Prerequisites

To begin this tutorial, ensure you have the following:

Setting up the project

To begin building our bookstore application, let’s create a blank Xcode playground by opening an Xcode application and selecting Empty in the Other tab.

Create A Blank Playground By Opening The Other Tab And Selecting Empty

Next, create a new playground file inside the newly created directory and name it.

Create And Name New Playground File, Shown As Blank Playground In iOS Tab

There should be default code in the file; modify it to the code below and click the Play button to ensure the code is running:

import UIKit

var str = "Hello, playground"

print(str)

Running this application prints Hello, playground on the console.

Running The Application Prints "Hello, Playground" In The Bottom Console

We made a custom demo for .
No really. Click here to check it out.

With all this done, we are ready to start building.

Building an application with Swift

Since we are building a simple bookstore application, we must create two classes: one for the user purchasing a book and the other for the purchased item.

The User class contains the user’s name and available balance, while the Item class contains the name of the book, price of the book, and the quantity of the book left in the store:

class User {
    var name: String
    var walletBalance: Double

    init(name: String, walletBalance: Double) {
        self.name = name
        self.walletBalance = walletBalance
    }
}


class Item {
    var bookName: String
    var price: Double
    var qtyLeft: Int

    init(bookName: String, price: Double, qtyLeft: Int){
        self.bookName = bookName
        self.price = price
        self.qtyLeft = qtyLeft
    }
}

In our example, a user has a specific wallet balance to purchase a book from the store. To ensure this can be done within our app, we must create a default user and book:

let user1 = User(name: "Wisdom Ekpot", walletBalance: 2000)
let storeItem = Item(bookName: "Oliver Twist", price: 1000, qtyLeft: 12)

Next, let’s create a function that allows the user to purchase a book from the store:

func purchaseBookFromStore (user: User, book: Item){
    user.walletBalance -= book.price
    book.qtyLeft -= 1

    print("\(user.name) successfully purchased \(book.bookName) from the store at \(book.price) naira")
    print("Total number of books remaining = \(book.qtyLeft)")
    print("New wallet balance =  \(user.walletBalance)")
}

This function takes in the User class and Item class as parameters. The function then deducts the book price from the user’s wallet balance and reduces the quantity of the Oliver Twist book available to purchase.

After reducing both quantities, the function prints out the number of books left and the user’s wallet balance. To run the program, we must call the function name and pass the User and Item classes as parameters:

purchaseBookFromStore(user: user1, book: Item)

Click the Play button to run the application. Once the program finishes compiling, the following prints on the console:

The User And Item Classes Render The Associated User and Book Information In The Console

Error test cases in a Swift app

Two major issues can arise in this application that we haven’t considered:

  1. What if the user’s wallet balance can’t afford a book in the store?
  2. What if the quantity of books in the store is less than the amount the user wants to purchase?

Let’s test our current code with these test cases individually.

Price error

If we set the user’s wallet balance to $500 with let user1 = User(name: "Wisdom Ekpot", walletBalance: 500), and then try to purchase $1000 of Oliver Twist book copies with let storeItem = Item(bookName: "Oliver Twist", price: 1000, qtyLeft: 12), the following prints on the console:

The Price Error Prints -500.0 In The Console

The new wallet balance of the user is now -500.0, which is an error. Instead of deducting the total book price from the user’s wallet, we must print an error message when the user’s wallet balance is less than the book price.

Quantity error

Another test case that we can encounter in our shopping app is when there are fewer books in the store than books needed by a user.

If we set the quantity of the Oliver Twist book in the store to 0 with let storeItem = Item(bookName: "Oliver Twist", price: 1000, qtyLeft: 0), and then call the purchaseBookFromStore function to buy 1 book, the console prints the following:

Quantity Error Prints -1 On The Console

As we can see, the total number of books in the store is now -1. Again, printing an error message here would let the user know that the number of books in the store is less than the amount required.

Handling errors in a Swift app

To fix these error cases, let’s write a condition to check whether the user can afford this book and whether the quantity of the book available is less than the quantity required by the user. If the user cannot afford the book or there is not enough quantity of the book, we can throw an error.

Inside the purchaseBookFromStore function block, add this condition before performing any operation:

if user.walletBalance < book.price || book.qtyLeft <= 0 {
        throw NSError()
}

At this point, an error throws if the condition is met, and the code below the condition won’t execute.

We must also ensure this function has the ability of throwing an error. To do this, we must modify our purchaseBookFromStore function with the following:

func purchaseBookFromStore (user: User, book: Item) throws {

    if user.walletBalance < book.price || book.qtyLeft <= 0 {
        throw NSError()
    }
    user.walletBalance -= book.price
    book.qtyLeft -= 1

    print("\(user.name) successfully purchased \(book.bookName) from the store at \(book.price) naira")
    print("Total number of books remaining = \(book.qtyLeft)")
    print("New wallet balance =  \(user.walletBalance)")
}

Notice the throws keyword before the curly brackets; this modifies how we are calling the function and indicates it can throw an error.

Next, we must wrap our purchaseBookFromStore function in a do-catch block. If the function returns an error, the catch block catches the error and prints it out on the console:

do{
    try purchaseBookFromStore(user: user1, book: storeItem)
}catch {
    print("something went wrong")
}

If we run our application again, we get the following on the console. Remember to use any of the error test cases we mentioned earlier to get the error message on the console.

"Something Went Wrong" Message Prints In The Console

Throwing a defined error

With the current state of our application, the function is not throwing the appropriate error to tell us what went wrong. To do this, we must define custom errors that we want to throw using a Swift enum, which conforms to the built-in Swift error class.

Swift enums are particularly suited for modeling a group of related error conditions because it groups related values and enables us to work with those values in a type-safe way within the code:

enum PurchaseError: Error {
    case InsufficentWalletBalance
    case InsufficentBookQty
}

With this enum defined, we must break the condition in our purchaseBookFromStore function into two separate conditions like the following:

  if user.walletBalance < book.price{

    }

    if  book.qtyLeft <= 0 {

    }

Now, if the user has an insufficient wallet balance, our condition looks like this:

 if user.walletBalance < book.price{
        throw PurchaseError.InsufficentWalletBalance
    }

If there are fewer books in the store compared to how many the user wants to buy, our condition looks like this:

 if  book.qtyLeft <= 0 {
        throw PurchaseError.InsufficentBookQty
  }

Finally, we can now catch the individual errors like so:

do{
    try purchaseBookFromStore(user: user1, book: storeItem)
}catch PurchaseError.InsufficentWalletBalance {
    print("You don't have sufficent funds to carry out this transaction")
}catch PurchaseError.InsufficentBookQty{
    print("Insufficent item quantity")
}catch{
    print("Something went wrong")
}

The last catch block acts as a default catch block when throwing an undefined error.

Conclusion

Handling errors and displaying custom errors are essential for any successful application. By defining errors and utilizing the do-catch feature in Swift, we can understand why they occur and solve issues faster. You can get the entire source code for this project on GitHub.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Wisdom Ekpot A student of Ibom Metropolitan Polytechnic studying computer engineering, Wisdom has been writing JavaScript for two years, focusing on Vue.js, Angular, and Express.js.

Leave a Reply