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.
To begin this tutorial, ensure you have the following:
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.
Next, create a new playground file inside the newly created directory and name it.
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.
With all this done, we are ready to start building.
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:
Two major issues can arise in this application that we haven’t considered:
Let’s test our current code with these test cases individually.
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 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.
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:
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.
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.
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.