Swift Closure Tutorial

by Dimitris Tasios
101 views

In this article, we will learn about a special type of Swift, the closure.

1. What Is a Closure in Swift?

A closure in Swift is a type (just like integers and strings) that can hold actual code. You can think of it as a function but, unlike functions, they are not created with the func keyword. Just like functions, however, they are enclosed in curly brackets ({}).

You might have already met some closures, especially when developing apps using Swift’s UIKit. For instance, whenever you want to present a view controller, you call the following function:

present(_ viewControllerToPresent: UIViewController, animated: Bool, completion: (() -> Void)?)

In this case, the completion argument is a closure (specifically, an optional closure) of type (() -> Void)?. Whenever this function ends, the code inside the closure will be executed. This is useful if we want to perform some actions after presenting a view controller.

2. How to Read a Swift Closure?

The aforementioned example might seem quite cumbersome but, we can break it down after taking a look into the syntax of a Swift closure:

{ (parameters or _) -> (return type or Void) in
    (statements)
}

As you can see, a closure is made of three parts:

  1. The paramaters of the closure.
  2. A return type or Void if the closure doesn’t return anything back.
  3. And, lastly, the statements.

With the exception of the in keyword that acts as a way to enclose the statements inside the closure (and not using curly brackets again), the syntax of a Swift closure is almost identical to one of a function. Note that if we have neither parameters nor return values, the syntax is simplified to only include the statements, as such:

{
    (statements)
}

Now, if we were to break down the completion closure of the present method, we can see that it has no parameters and doesn’t return anything back. The surrounding brackets with the question-mark (?) means that the closure is optional, so if we didn’t want to do anything after presenting a view controller, we could put would write completion: nil.

3. Using a Swift Closure

In this section, we will see how to make our own closures and use them in other parts of our code.

3.1 A Swift Closure Without Parameters or Return Value

Starting off with the simplest form of a closure in Swift, this kind of closure has neither parameters nor return values.

let helloWorldClosure = {
    print("Hello World!")
}

helloWorldClosure()

The output is:

Hello World!

Note that in order to use the closure, we use a similar syntax to a function. In this case, since we had no arguments, a simple pair of brackets was enough to call the closure and print our message.
Here, the type of the closure is () -> Void.

3.2 A Closure With Parameters but No Return Value

Moving on, let’s see how to write a closure that has some parameters. We will provide two integers as parameters and print their sum.

let sumTwoIntegersClosure = { (firstOperand: Int, secondOperand: Int) in
    print("The sum is \(firstOperand + secondOperand).")
}

sumTwoIntegersClosure(10, 3)

The output is:

The sum is 13.

We are, once again, calling the sumTwoIntegersClosure closure like we would for a normal function.
The type of this closure is (Int, Int) -> Void.

If we wanted to improve the sumTwoIntegersClosure to add as many integers as we wanted, we would do something like the following:

let sumIntegersClosure = { (operands: Int...) in
    let sum = operands.reduce(0, +)
    print("The sum is \(sum).")
}

sumIntegersClosure(3, 7, 55, 100)
sumIntegersClosure(99, -4, 1)
sumIntegersClosure(5)

The output is:

The sum is 165.
The sum is 96.
The sum is 5.

Let’s see what happened here:

  • Line 1: In this example, our closure accepts any number of integers, with the help of the variadic parameter operands, indicated by the ... after the type.
  • Line 2: For the purposes of this article, we will only say that the reduce method has two parameters: the first parameters defines an initial value (in our case 0 since we are making a sum) while the second parameter applies the operation to all elements of an array (in our case we are adding integers). As a result, the array is “reduced” to a single value, the sum of all of its elements.
  • Lines 6-8: Thanks to the variadic Int parameter operands, we can call the sumIntegersClosure closure, each time withe a different number of integers and always getting the sum of all of them.

The type of this closure is (Int...) -> Void.

3.3 A Closure With a Return Type but No Parameters

Let’s move on to the other side now, a closure with a return type but no arguments. The following closure returns a random number between 1 and 100, including both of those values. We then call that closure 10 times, by using a for loop.

let randomNumberClosure = { () -> Double in
    return Double.random(in: 0...100)
}

for index in 1...10 {
    print("\(index). Random number: \(randomNumberClosure())")
}

The output is:

1. Random number: 45.12128687152408
2. Random number: 93.36114471107685
3. Random number: 26.66604797338867
4. Random number: 95.343345999936
5. Random number: 15.3303099156193
6. Random number: 42.82582869394359
7. Random number: 54.107789511564455
8. Random number: 17.202474970843785
9. Random number: 41.496607275961914
10. Random number: 27.398843073863443

Note that the output will be different each time we run the for loop, so your output will be different.

As you can see, the randomNumberClosure closure has no arguments which are defined with an empty pair of brackets (). The return value is Double so we have to return a Double number inside the closure or we’ll get an error.

The type of this closure is () -> Double.

3.4 A Complete Closure

Finally, let’s see a closure that puts everything together. The closure of this example accepts a String as an argument and returns it in upper case.

let capitalizeStringClosure = { (theString: String) -> String in
    return theString.uppercased()
}

let phrase = capitalizeStringClosure("The quick brown fox jumps over the lazy dog.")
print(phrase)

The output is:

THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.

In this example, we use the built-in function uppercased() at the argument theString and return the uppercased string back to the caller. In our case, the returned value is saved in a constant named phrase, which is then printed.

The type of this closure is (String) -> String.

4. Using a Closure as a Function Parameter

One of the most common uses of a Swift closure is by making it an argument for another function. Since a Swift closure is a type, it can be used as a function’s parameter, just like a normal Int or String.

Consider this example. We want to make a function that accepts 2 numbers and a closure and applies the operation to those 2 numbers. Have a look at how we would accomplish that:

func applyOperation(firstOperand number1: Double,
                    secondOperand number2: Double,
                    operationClosure closure: (Double, Double) -> Double) -> Double {
    print("Applying arithmetic operation between \(number1) and \(number2)")
    
    let result = closure(number1, number2)
    return result
}

let additionClosure = { (num1: Double, num2: Double) -> Double in
    return num1 + num2
}

let addtionOperationResult = applyOperation(firstOperand: 4, secondOperand: 5, operationClosure: additionClosure)
print("The addition result is \(addtionOperationResult).")

print()

let multiplicationClosure = { (num1: Double, num2: Double) -> Double in
    return num1 * num2
}

let multiplicationOperationResult = applyOperation(firstOperand: 18, secondOperand: 25, operationClosure: multiplicationClosure)
print("The multiplication result is \(multiplicationOperationResult).")

The output is:

Applying arithmetic operation between 4.0 and 5.0
The addition result is 9.0.

Applying arithmetic operation between 18.0 and 25.0
The multiplication result is 450.0.

Things got very complicated all of a sudden, but worry not! Let’s see how everything works.

  • Lines 1-3: The function applyOperation takes 3 arguments: 2 Double numbers and a closure
    of type (Double, Double) -> Double, or a closure that accepts two Double numbers and returns a Double. It finally return a Double of its own, which is basically the number that the closure returned. We also print a message that informs us about which the two operands are. For more clarity, we have splitted the function’s signature in 3 lines, and we suggest to do the same, for the sake of your sanity!
  • Line 6: The closure is called and its result is saved in a variable named result. The function, then, returns that value.
  • Lines 10-12: We define a closure of type (Double, Double) -> Double, the same as the function’s (applyOperation) parameter (operationClosure), that adds 2 numbers and returns the result.
  • Line 14: The function is called with two numbers and the closure that adds them (additionClosure). The sum is returned (addtionOperationResult) so it can be printed in the next line.
  • Lines 19-24: We do the same steps but with a multiplication operation instead.

As you can see, the fact that our function can accept any closure of type (Double, Double) -> Double makes it very versatile and reusable in many different cases. So you can imagine how many things an even more generic closure type
such as () -> Void could do.

5. Trailing Closures

A trailing closure is no different than the closures we have mentioned so far. The difference comes in syntax. Whenever we have a function that has a closure as its last argument, we can write that closure outside of the function’s brackets, in curly brackets. This can be done with multiple closures as well, as long as they’re the last argument of the function (or equally, there isn’t another non-closure argument after them).

func applyOperationOnNumber(number: Int, operationClosure: () -> Void) {
    print("The number is: \(number)")
    operationClosure()
}

// Calling the function without the trailing closure syntax
applyOperationOnNumber(number: 34 , operationClosure: {
    print("Its double is: \(34 * 2)")
})

print()

// Calling the function with a trailing closure syntax
applyOperationOnNumber(number: 21) {
    print("Its power of two is: \(21 * 21)")
}

The output is:

The number is: 34
Its double is: 68

The number is: 21
Its power of two is: 441

As you can notice, calling the function either way, with a trailing closure or not, results in similar functionality; The number is printed and then the closure is called, each time with a different action.

Additionally, we are able to have multiple trailing closures. We have to write the argument labels starting from the second trailing closure onward, but not the argument label from the first closure, like so:

func performActionsAfterPrinting(message: String, firstAction: (() -> Void), secondAction: (() -> Void)) {
    print(message)
    firstAction()
    secondAction()
}

print()

performActionsAfterPrinting(message: "First message") {
    print("Another message")
} secondAction: {
    let randomInteger = Int.random(in: 0...10)
    print("A random number: \(randomInteger)")
}

The output is:

First message
Another message
A random number 5

We didn’t have to write the argument label “firstAction” for the first action but, we had to do so for the second, so we have to write the “secondAction” argument label.

6. Capturing Values With Closures

A Swift closure can capture values outside of its scope, if it needs them, even if the scope the values exist no longer exists. That sounds very complicated so let’s see what we mean with an example.

We want to create a function that creates an incrementer function and returns it. The incrementer function simply increments by two every time it’s called. So basically, we have a function that returns a closure and that closure takes a number, increases it by 2, and returns it. We will then call the returned closure 5 times to see if our function worked. Let’s see our first attempt at doing that:

func makeNextEvenNumberFunctionIncorrect() -> () -> Int {
    return {
        var number = 0
        number += 2
        return number
    }
}

let getNextEvenNumberClosureIncorrect = makeNextEvenNumberFunctionIncorrect()
for _ in 1...5 {
    print(getNextEvenNumberClosureIncorrect())
}

The output is:

2
2
2
2
2

Well, as the name implied, that function didn’t quite do what we wanted it to do. Let’s see what went wrong.

Inside the makeNextEvenNumberFunctionIncorrect we return a new closure of type () -> Int. In that closure, we define a number called number (line 3), increase it by 2 and return it to the closure. We then call the function in line 9 and call the closure it returns 5 times, in line 11.

The problem with that is that in line 3, every time we call that function (makeNextEvenNumberFunctionIncorrect) this value starts from 0, so it always returns a closure that returns the number 2.

Let’s try to move that statement in line 3 outside of the closure.

func makeNextEvenNumberFunctionCorrect() -> () -> Int {
    var number = 0
    return {
        number += 2
        return number
    }
}

let getNextEvenNumberClosureCorrect = makeNextEvenNumberFunctionCorrect()
for _ in 1...5 {
    print(getNextEvenNumberClosureCorrect())
}

The output is:

2
4
6
8
10

You can see that it now works as intended, so what happens here?

By moving the statement that initializes the number to 0 outside of the closure, the closure is capturing that value so that it stays alive (and not be deallocated), even if the makeNextEvenNumberFunctionCorrect is deallocated. It’s like storing the value in a safe and no matter what happens outside of it (in the function, in our case) it will be there until it’s needed again.

As a result, by calling the getNextEvenNumberClosureCorrect closure five times, the number returned is incremented by 2 from the last time it was called.

7. Escaping Closures

Normally, if we define a closure in a function, it lives within the function. But sometimes, we might only want our closure to outlive the function it exists in. To do that, we have to use the @escaping annotation, which means that the closure escapes from the function and continues to live.

For instance, let’s say that we wanted to save a closure to a variable and had a function that sets that closure to our variable. We would end up with something like this:

var printAction = {}

func setPrintAction(action: @escaping () -> Void) {
    print("The action of the escapingFunction will be set.")
    printAction = action
}

setPrintAction(action: {
    print("printAction will print this now")
})

printAction()

The output is:

The action of the escapingFunction will be set.
printAction will print this now

Let’s see what happened:

  • Line 1: We define a variable that is of type () -> Void. This can be updated later, with the help of our function setPrintAction.
  • Line 3 & 5: Here, if you remove the @escaping anotation, you would get an error in line 5, because in the latter line we assign the parameter closure action to a variable that outlives the function, printAction. Since we want to simple set printAction with an action and not call that action inside the function, we must declare the action parameter as @escaping.
  • Line 8: We set the print() action to our printAction.
  • Line 12: We call our printAction closure. The setPrintAction function no longer exists, but its action parameter, which in turn is printAction, exists.

8. Conclusion

By now, you should be able to create a closure in Swift, as well as, use it in various situations. You can find the source code (Playground) on our GitHub page.

Related Posts