< When would closures with parameters be used as parameters? | When should you use shorthand parameter names? > |
Updated for Xcode 14.2
Swift’s closures can return values as well as take parameters, and you can use those closures in functions. Even better, those functions can also return values, but it’s easy for your brain to get a bit fuzzy here because there’s a lot of syntax.
To demonstrate a common usage for this kind of closures, we’re going to implement a simple reducer method. This is a common behavior that is designed to summarize arrays – to take a lot of numbers, or strings, or whatever, and boil them down to a single value.
In our simplified example, our reducer will accept two parameters: an array of numbers, and a closure that can reduce that array down to a single value. For example, it might accept an array of numbers and add them together, then return the final total. To do that, the closure will accept two parameters: one to track the current value (everything that been reduced so far), and the current value that needs to be added to the reduced value. The closure will also return a value, which is the new reduced value, and the whole function will return the fully reduced value – the total of all numbers, for example.
For example, if we wanted to reduce the array [10, 20, 30]
, it would work something like this:
current
with a value set to the first item in its array. This is our starting value.1...
so that it counts from index 1 to the end.In code it looks like this:
func reduce(_ values: [Int], using closure: (Int, Int) -> Int) -> Int {
// start with a total equal to the first value
var current = values[0]
// loop over all the values in the array, counting from index 1 onwards
for value in values[1...] {
// call our closure with the current value and the array element, assigning its result to our current value
current = closure(current, value)
}
// send back the final current value
return current
}
With that code in place, we can now write this so add up an array of numbers:
let numbers = [10, 20, 30]
let sum = reduce(numbers) { (runningTotal: Int, next: Int) in
runningTotal + next
}
print(sum)
Tip: In that code we’re explicit that runningTotal
and next
will both be integers, but we can actually leave out the type annotation and Swift will figure it out. Notice that we haven’t had to say our closure returns an integer, again because Swift can figure that out for itself.
The great thing here is that reduce()
doesn’t care what its closure does – it only cares that it will accept two integers and return one integer. So, we could multiply all the items in our array like this:
let multiplied = reduce(numbers) { (runningTotal: Int, next: Int) in
runningTotal * next
}
Although this was just an example to demonstrate why closures with return values make useful functional parameters, I want to mention three more things.
First, our reduce()
function uses values[0]
for its initial value, but what happens if we call reduce()
with an empty array? We get a crash – that’s what happens. Clearly that isn’t a good thing, so you wouldn’t want to use this code outside of a learning sandbox.
Second, I mentioned previously that Swift’s operators are actually functions in their own right. For example, +
is a function that accepts two numbers (e.g. 5 and 10) and returns another number (15).
So, +
takes two numbers and returns a number. And our reduce()
function expects a closure that takes two numbers and returns a number. That’s the same thing! The +
function fulfills the same contract as reduce()
wants – it takes the same parameters and returns the same value.
As a result, we can actually write this:
let sum = reduce(numbers, using: +)
Yes, really. Neat, huh?
Third, this reduce()
function is so important that a variant actually comes with Swift as standard. The concept is the same, but it’s more advanced in several ways:
Even better, it won’t crash when used on an empty array!
This took quite a bit of explaining, but I hope it’s given you a practical example of why closures that return values can be useful as parameters. As you progress in your skills you’ll learn many more examples – it’s surprisingly common.
SPONSORED Thorough mobile testing hasn’t been efficient testing. With Waldo Sessions, it can be! Test early, test often, test directly in your browser and share the replay with your team.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.