The best ways to make sure your Swift code never crashes again

arteko
arteko
Dec 24, 2019 · 4 min read
Icons come from Freepik sites

All programs have situations where they can create errors.

This is almost inevitable but it is normal because we cannot predict the behaviour of the user of our program.

Rather than preventing all errors from occurring, we have to learn how to deal with them.

You have to learn how to manage them so that the program doesn’t crash and the user leaves.

Often, we forget to manage errors because we tell ourselves that it won’t happen or that it can’t happen.

But you can never be sure, so you have to be rigorous with that.

Guard let vs If let

The optionals are the first source of error in Swift.

When you unwrap them to access their value, you never know what will be there in advance.

Unless you specify it yourself beforehand.

Here’s how to unwrap them safely with guard let:

guard let url = URL(string: “https://medium.com") else { return }

Using guard let is used to create a variable that will be accessible in the scope of the function that contains that declaration.

It is also used to make the function stop if you don’t get the value you want.

If ever, in a function, you don’t want to stop the function if you don’t have the value you want, or if you do something different depending on whether it’s there or not, then you can use if let:

if let data = rawData as? [String: [String: String]] {
print("converted data successfully")
}
else if let data = rawData as? [String: [String]] {
print("second attempt worked")
}
else {
print("Error")
}

I recommend you try to use guard let as much as possible because your code will be cleaner.

It avoids having a lot of nested if let if you have several options to handle which would not be very readable.

Try … catch

Sometimes we also have to handle cases in which we will return a subclass of the Error object.

Functions that are likely to return errors are marked are defined like this:

func canThrowErrors() throws -> String

If a function is declared like this, you must call it in a try … catch block:

do {
try canThrowErrors()
}
catch ErrorSubclass {
print(error)
}

In this code, if the function returns an error of the type ErrorSubclass then we execute the code in the catch block.

Here are the different patterns you can use in a catch:

do {    try expression
statements
} catch ErrorType { statements} catch ErrorType where condition { statements} catch { // catch any error statements
}

You can create your own errors based on what your code does.

And it is even strongly recommended to have full control over all possible situations in your code.

Here is an example:

enum EcommerceError: Error {    case InvalidProduct    case NotAvailable    case NotEnoughMoney(price: Int)    case InvalidAddress    case InvalidPaymentInformation}

This code will create a new type of error which is EcommerceError.

Here’s how we can use it:

func buyItem(items: [Int: Int], item: [String: Int], address: String, paymentInfo: [String: Int]) throws {    guard items.contains(item["number"]) else { throw EcommerceError.InvalidProduct }    guard items[item["number"]] > 0 else { throw EcommerceError.NotAvailable }    guard paymentInfo["money"] >= item["price"] else { throw EcommerceError.NotEnoughMoney(price: item["price"]) }    guard address.isValidAddress() else { throw EcommerceError.InvalidAddress }    guard isPaymentInfoValid(info: paymentInfo) else { throw EcommerceError.InvalidPaymentInformation }    // run code to buy item}func isPaymentInfoValid(info: [String: Int]) -> Bool {    // check if info is correct    return true}extension String {    func isValidAddress() -> Bool {        // check if string is address        return true    }}

In this code, every information about the product and its buyer is checked and depending on the possible error a certain type of error is returned.

This is how we would use the function buyItems:

let items = [100: 2, 430: 5, 1238: 19]let item = ["number": 100, "price": 50]let address = "valid address"let paymentInfo = ["card": 4242424242424242, "month": 2, "year": 21, "ccv": 100, "money": 100]do {    try buyItems(items: items, item: item, address: address, paymentInfo: paymentInfo)}
catch EcommerceError.InvalidProduct {
print("Invalid Product.")}
catch EcommerceError.NotAvailable {
print("Out of Stock.")}
catch EcommerceError.NotEnoughMoney(let price) {
print("Insufficient money. The price is: \(price)")}
catch EcommerceError.InvalidAddress {
print("Please enter a valid address")}
catch EcommerceError.InvalidPaymentInformation {
print("Could not proceed payment due to invalid payment information")}
catch {
print("Unexpected error: \(error).")}

In this code a different error is displayed depending on what went wrong in the buyItems function and the type of error created is used to warn the user of what went wrong.

Conclusion

With this article, you have everything you need to manage your code errors as well as possible.

It will be extremely useful to avoid crashing the application you are creating and thus losing users.

Thanks for reading this article!

arteko

Written by

arteko

Freelance iOS developer

Flawless iOS

🍏 Community around iOS development, mobile design, and marketing

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade