A function is a named piece of code that you can run whenever you need it. A closure is a function without a name that you can pass around like a value. SwiftUI uses both on nearly every line, so this lesson is time well spent. We keep the Expense Tracker idea: amounts of money and what we do with them.
Writing a function
A function starts with func, then a name, then its inputs in brackets, then an arrow and the type it gives back. Swift functions have a special habit: each input has an argument label that you write when you call the function. This makes calls read like short sentences.
func total(of amounts: [Double]) -> Double {
var sum = 0.0
for a in amounts { sum += a }
return sum
}
let t = total(of: [12.5, 40, 7.25])
print(t) // 59.75
Look at total(of amounts:). The first word, of, is the label used when you call it. The second word, amounts, is the name used inside the function. If you do not want a label, write an underscore: func total(_ amounts: [Double]). Then you call total([1, 2]). If a function gives nothing back, you leave out the arrow.
Default values
An input can have a default. Callers may then leave it out. This keeps simple calls short and still allows detail when you need it.
func format(_ amount: Double, currency: String = "$") -> String {
return "\(currency)\(String(format: "%.2f", amount))"
}
print(format(5)) // $5.00
print(format(5, currency: "EUR ")) // EUR 5.00
Closures
A closure is a block of code in curly braces. Its inputs come first, followed by the word in. You can store it in a constant, pass it to a function, or return it.
let double = { (x: Double) -> Double in
return x * 2
}
print(double(4)) // 8.0
func repeatTask(times: Int, action: () -> Void) {
for _ in 0..<times { action() }
}
repeatTask(times: 3) {
print("Saved a backup")
}
Look at the last call. When the last input of a function is a closure, you can write the closure *after* the brackets. This is called a trailing closure. You will see this shape in SwiftUI all the time, for example in Button("Add") { ... }, where the braces hold the code that runs when someone taps.
map, filter, sorted and reduce
Arrays have helpers that take a closure. They replace many small loops, and they read as a description of what you want.
let amounts = [12.5, 40, 7.25, 99, 3]
let doubled = amounts.map { $0 * 2 }
let large = amounts.filter { $0 > 10 }
let sorted = amounts.sorted { $0 > $1 }
let sum = amounts.reduce(0) { $0 + $1 }
print(doubled) // [25.0, 80.0, 14.5, 198.0, 6.0]
print(large) // [12.5, 40.0, 99.0]
print(sorted) // [99.0, 40.0, 12.5, 7.25, 3.0]
print(sum) // 161.75
mapmakes a new array by changing each item.filterkeeps only the items for which the closure says true.sortedputs the items in order. The closure says which of two items comes first.reducefolds all items into one value. The first input is the starting value, here 0.$0and$1are short names for the first and second inputs. They are fine for tiny closures. When a closure grows, give the inputs real names, because the code is easier to read later.
Functions inside types
When a function lives inside a struct or class, it is called a method. In the next lesson you will put total inside a type that holds expenses. Closures also *capture* the values they use from the code around them. This is powerful, and also the reason that, in classes, you sometimes meet the words [weak self] in longer examples. We come back to this when it matters.
Common mistakes
- Forgetting the argument label when you call a function. Swift's error message names the missing label.
- Writing a very long closure on one line. Split it onto several lines with named inputs.
- Using
reducewhen a plain loop would be clearer. Choose the one a teammate would understand fastest.
Your task
- Write
average(of:)that takes an array ofDoubleand returns the average. Return 0 for an empty array. - Write
isLarge(_:limit:)with a default limit of 50 that returns aBool. - Given an array of amounts, use
filterto keep those above 20, thenmapto add 10 per cent, thenreduceto total them. - Call your own function with a trailing closure, like
repeatTaskabove.
How to check it worked
average(of: [10, 20, 30])prints 20.0 andaverage(of: [])prints 0.0.- You can explain what
$0means. - You can say what a trailing closure is and find one in the examples.