Skip to main content
  1. SwiftUI in 100 Days Notes/

Day 8 - Swift Functions - 2 : Default Parameters and Error Handling

Default Value for Function Parameters #

We have already mentioned that we can customize our functions through parameters. We can make this customization even more optional by providing default values to our parameters.

Let’s examine a function we wrote in a previous lesson;

func printTimesTables(for number: Int, end: Int) {
    for i in 1...end {
        print("\(i) x \(number) is \(i * number)")
    }
}

printTimesTables(for: 5, end: 20)

The code above gives the multiplication table of any number we want starting from 1 to any point. The endpoint can always be variable, but most of the time we want it to be 10 or 12. But sometimes it may also need to go to an endpoint that we specify.

To solve this problem, Swift allows us to assign default values to function parameters. In the code below, the value 12 is set as the default value for the end parameter. Unless we specify otherwise during the function call, the value 12 will be used.

func printTimesTables(for number: Int, end: Int = 12) {
    for i in 1...end {
        print("\(i) x \(number) is \(i * number)")
    }
}

printTimesTables(for: 5, end: 20)
printTimesTables(for: 8)

We can call the printTimesTables() function in two different ways. We can provide two parameters (for: end:), or we can provide one parameter (for:) and use the default value of the other parameter.

For optimization, Swift allocates a little more than enough space to hold the Array elements, increasing the allocated space as the size of the Array increases. This uses as little memory as possible.

var characters = ["Lana", "Pam", "Ray", "Sterling"]
print(characters.count)
characters.removeAll()
print(characters.count)

When the removeAll() function is called, Swift will automatically remove all items from the Array and free the allocated memory. However, in some cases we may want to remove Array items but keep the allocated memory (we may want to remove all items in an Array and add many new items). Here the removeAll() function also has a functionality where it removes the items and keeps the memory space.

characters.removeAll(keepingCapacity: true)

This behavior of the removeAll() function is achieved thanks to the default parameter property of functions. The default value of the keepingCapacity parameter is false. This means that it both removes items and clears memory space (it will remain false unless we specifically specify it during the function call). If we want to preserve the memory space of the Array, we change this parameter to true.

Error Handling in Functions #

Situations such as the file we want to read not being available, or the data we are trying to download failing because the internet connection is interrupted, can happen at any time. If we don’t handle these errors properly, our code may crash. For this reason, we need to determine what behavior to take according to the errors that may occur in the functions.

Error Handling consists of three steps;

  1. Inform Swift of possible errors that may occur.
  2. Write a function that can notify when an error occurs.
  3. Calling the function and handling possible errors.

Let’s write a function that checks the user’s password, generating an error if the password is too short or too easy.

Inform Swift of possible errors that may occur. #

Let’s start by defining the possible errors that can occur. This requires us to create an enum based on the Error type.

enum PasswordError: Error {
    case short, obvious
}

The enum above says that there are two possible error cases: short and obvious. This enum does not define what they mean, it just says that they exist.

Write a function that can notify when an error occurs. #

In our second step, we will write a function that can throw these errors. When an error is triggered, the function will terminate immediately without returning a value, instead of continuing normally.

func checkPassword(_ password: String) throws -> String {
    if password.count < 5 {
        throw PasswordError.short
    }

    if password == "12345" {
        throw PasswordError.obvious
    }

    if password.count < 8 {
        return "OK"
    } else if password.count < 10 {
        return "Good"
    } else {
        return "Excellent"
    }
}

Let’s analyze the code above a bit;

  • If a function can throw errors without handling them itself, it is marked as throws before the return type of the function.
  • We don’t specify exactly what kind of error will be thrown by the function, we just say that it can throw errors.
  • The fact that the function is marked with throw does not necessarily mean that it will throw an error, only that it might.
  • When an error occurs, we write throw and one of the cases PasswordError. This exits the function immediately, so it does not return a String.
  • If no error is thrown, the function behaves normally, i.e. it returns a String.

Calling the function and handling possible errors. #

This step consists of three sub-steps.

  1. Initialize a block of code with do that may give an error,
  2. Call one or more throw functions using try,
  3. Handling thrown errors using catch

Let’s continue with our example;

let string = "12345"

do {
    let result = try checkPassword(string)
    print("Password rating: \(result)")
} catch {
    print("There was an error.")
}

//OUTPUT:
//----------------------------------------
//There was an error.

If the checkPassword() function works without errors, it returns a value and this value is assigned to the result constant. However, if an error occurs (which will happen according to the code above), the "Password rating:" part will not be printed to the terminal and the program flow will be redirected to the catch block.

try should be written before calling all functions that may throw an error, this is a visual signal to developers that if an error occurs, the normal code flow will be interrupted.

do {
    try throwingFunction1()
    nonThrowingFunction1()
    try throwingFunction2()
    nonThrowingFunction2()
    try throwingFunction3()
} catch {
    // handle errors
}

In order to use try, we must be in a do block and have one or more catch blocks that can handle any error. In very rare cases, when we are absolutely sure that the function will not throw an error, we can use try!. When try! is used, the do and catch blocks are not needed.

We can also catch certain errors with the catch block. But we have to make sure that every error is caught.

let string = "12345"

do {
    let result = try checkPassword(string)
    print("Password rating: \(result)")
} catch PasswordError.short {
    print("Please use a longer password.")
} catch PasswordError.obvious {
    print("I have the same combination on my luggage!")
} catch {
    print("There was an error.")
}
//OUTPUT:
//----------------------------------------
//I have the same combination on my luggage!

You can also read this article in Turkish.
Bu yazıyı Türkçe olarak da okuyabilirsiniz.

This article contains the notes I took for myself from the articles found at SwiftUI Day 8. Please use the link to follow the original lesson.