Chapters

Hide chapters

Swift Apprentice: Fundamentals

First Edition · iOS 16 · Swift 5.7 · Xcode 14.2

Section III: Building Your Own Types

Section 3: 9 chapters
Show chapters Hide chapters

5. Functions
Written by Matt Galloway

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Functions are a core part of many programming languages. Simply put, a function lets you define a block of code that performs a task. Then, whenever your app needs to execute that task, you can run the function instead of copying and pasting the same code everywhere.

In this chapter, you’ll learn how to write your own functions and see firsthand how Swift makes them easy to use.

Function Basics

Imagine you have an app that frequently needs to print your name. You can write a function to do this:

func printMyName() {
  print("My name is Matt Galloway.")
}

The code above is known as a function declaration. You define a function using the func keyword. After that comes the name of the function, followed by parentheses. You’ll learn more about the need for these parentheses in the next section.

After the parentheses comes an opening brace, followed by the code you want to run in the function, followed by a closing brace. With your function defined, you can use it like so:

printMyName()

This prints out the following:

My name is Matt Galloway.

If you suspect that you’ve already used a function in previous chapters, you’re correct! print, which prints the text you give to the console, is indeed a function. In the next section, you’ll learn how to pass data to a function and get data back when it finishes.

Function Parameters

In the previous example, the function simply prints out a message. That’s great, but sometimes you want to parameterize your function, which lets it perform differently depending on the data passed into it via its parameters.

func printMultipleOfFive(value: Int) {
  print("\(value) * 5 = \(value * 5)")
}

printMultipleOfFive(value: 10)
func printMultipleOf(multiplier: Int, andValue: Int) {
  print("\(multiplier) * \(andValue) = \(multiplier * andValue)")
}

printMultipleOf(multiplier: 4, andValue: 2)
func printMultipleOf(multiplier: Int, and value: Int) {
  print("\(multiplier) * \(value) = \(multiplier * value)")
}

printMultipleOf(multiplier: 4, and: 2)
pirr OlgOcj vjuncPasracreIr (ticsesmaol:, igl ravio:) Umjedwur geva Ibfekjar jisu

func printMultipleOf(multiplier: Int, and: Int)
func printMultipleOf(_ multiplier: Int, and value: Int) {
  print("\(multiplier) * \(value) = \(multiplier * value)")
}

printMultipleOf(4, and: 2)
func printMultipleOf(_ multiplier: Int, _ value: Int) {
  print("\(multiplier) * \(value) = \(multiplier * value)")
}

printMultipleOf(4, 2)
func printMultipleOf(_ multiplier: Int, _ value: Int = 1) {
  print("\(multiplier) * \(value) = \(multiplier * value)")
}

printMultipleOf(4)

Return Values

So far, all the functions you’ve seen have performed a simple task: printing something out. Functions can also return a value, and the caller of the function can assign the return value to a variable or constant or use it directly in an expression.

func multiply(_ number: Int, by multiplier: Int) -> Int {
  return number * multiplier
}

let result = multiply(4, by: 2)
func multiplyAndDivide(_ number: Int, by factor: Int)
                   -> (product: Int, quotient: Int) {
  return (number * factor, number / factor)
}

let results = multiplyAndDivide(4, by: 2)
let product = results.product             // 8
let quotient = results.quotient           // 2
func multiply(_ number: Int, by multiplier: Int) -> Int {
  number * multiplier
}

func multiplyAndDivide(_ number: Int, by factor: Int)
                   -> (product: Int, quotient: Int) {
  (number * factor, number / factor)
}

Advanced Parameter Handling

Function parameters are constants, which means they can’t be modified.

func incrementAndPrint(_ value: Int) {
  value += 1
  print(value)
}

func incrementAndPrint(_ value: inout Int) {
  value += 1
  print(value)
}
var value = 5
incrementAndPrint(&value)
print(value)

Overloading

Did you notice how you used the same name for several different functions in the previous examples?

func printMultipleOf(multiplier: Int, andValue: Int)
func printMultipleOf(multiplier: Int, and value: Int)
func printMultipleOf(_ multiplier: Int, and value: Int)
func printMultipleOf(_ multiplier: Int, _ value: Int)
func getValue() -> Int {
  31
}

func getValue() -> String {
  "Matt Galloway"
}
let value = getValue()

let valueInt: Int = getValue()
let valueString: String = getValue()

Mini-Exercises

  1. Write a function named printFullName that takes two strings called firstName and lastName. The function should print out the full name defined as firstName + " " + lastName. Use it to print out your own full name.
  2. Change the declaration of printFullName to have no external name for either parameter.
  3. Write a function named calculateFullName that returns the full name as a string. Use it to store your own full name in a constant.
  4. Change calculateFullName to return a tuple containing both the full name and the length of the name. You can find a string’s length by using the count property. Use this function to determine the length of your own full name.

Functions as Variables

This may come as a surprise, but functions in Swift are simply another data type. You can assign them to variables and constants like any other type, such as an Int or a String.

func add(_ a: Int, _ b: Int) -> Int {
  a + b
}
var function = add
function(4, 2)
func subtract(_ a: Int, _ b: Int) -> Int {
  a - b
}
function = subtract
function(4, 2)
func printResult(_ function: (Int, Int) -> Int, _ a: Int, _ b: Int) {
  let result = function(a, b)
  print(result)
}
printResult(add, 4, 2)

The Land of No Return

Some functions are never, ever intended to return control to the caller. For example, think about a function designed to halt an application. Perhaps this sounds strange, so let me explain: if an application is about to work with corrupt data, it’s often best to crash rather than continue into an unknown and potentially dangerous state. The function fatalError("reason to terminate") is an example of a function like this. It prints the reason for the fatal error and then halts execution to prevent further damage.

func noReturn() -> Never {

}

func infiniteLoop() -> Never {
  while true {
  }
}

Writing Good Functions

Functions let you solve many problems. The best do one simple task, making them easier to mix, match, and model into more complex behaviors.

Commenting Your Functions

All good software developers document their code. :] Documenting your functions is essential to making sure that when you return to the code later or share it with other people, it can be understood without having to trawl through the code.

/// Calculates the average of three values
/// - Parameters:
///   - a: The first value.
///   - b: The second value.
///   - c: The third value.
/// - Returns: The average of the three values.
func calculateAverage(of a: Double, and b: Double, and c: Double) -> Double {
  let total = a + b + c
  let average = total / 3
  return average
}

calculateAverage(of: 1, and: 3, and: 5)

Challenges

Before moving on, here are some challenges to test your knowledge of functions. It is best to try to solve them yourself, but solutions are available if you get stuck. These came with the download or are available at the printed book’s source code link listed in the introduction.

Challenge 1: Looping with Stride Functions

In Chapter 4, “Advanced Control Flow”, you wrote some for loops with countable ranges. Countable ranges are limited in that they must always be increasing by one. The Swift stride(from:to:by:) and stride(from:through:by:) functions let you loop much more flexibly.

for index in stride(from: 10, to: 22, by: 4) {
  print(index)
}
// prints 10, 14, 18

for index in stride(from: 10, through: 22, by: 4) {
  print(index)
}
// prints 10, 14, 18, and 22

Challenge 2: It’s Prime Time

When I’m acquainting myself with a programming language, one of the first things I do is write a function to determine whether or not a number is prime. That’s your second challenge.

func isNumberDivisible(_ number: Int, by divisor: Int) -> Bool
func isPrime(_ number: Int) -> Bool
isPrime(6)     // false
isPrime(13)    // true
isPrime(8893)  // true

Challenge 3: Recursive Functions

In this challenge, you will see what happens when a function calls itself, a behavior called recursion. This may sound unusual, but it can be quite useful.

func fibonacci(_ number: Int) -> Int
fibonacci(1)  // = 1
fibonacci(2)  // = 1
fibonacci(3)  // = 2
fibonacci(4)  // = 3
fibonacci(5)  // = 5
fibonacci(10) // = 55

Key Points

  • You use a function to define a task that you can execute as many times as you like without writing the code multiple times.
  • Functions are defined with zero or more parameters and optionally a return value.
  • You can add an external name to a function parameter to change the label you use in a function call, or you can use an underscore to denote no label.
  • Parameters are passed as constants, unless you mark them as inout, in which case they are copied-in and copied-out.
  • Functions can have the same name with different parameters. This is called overloading.
  • Functions can have a special Never return type to inform Swift that this function will never exit.
  • You can assign functions to variables and pass them to other functions.
  • Strive to create functions that are clearly named and have one job with repeatable inputs and outputs.
  • Function documentation can be created by prefixing the function with a comment section using ///.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now