What are Closures and how do they work in Swift? (Step-by-Step Guide)
This article explains the basics of closures. Concepts like escaping closures, completion handlers and linked lists are not discussed here, but will be in future posts :). For this guide, it is recommended to follow along in playgrounds and experiment with the code.
It is no secret that many aspiring IOS developers quiver in fear when they hear the word ‘closure’ (buh!! 👻🤪). Since you are reading this article, I think it is likely that you are one of them. But… why? Why are closures feared as much IF NOT MORE than giant, hairy spiders? Well, for one, unlike giant, hairy spiders, a closure is a very abstract concept that is hard to make concrete. In contrast to a struct or a class, which can easily be analogized to, for instance, a blueprint of a house to be constructed (while the initialization is the metaphorical construction of said house), a closure seems terribly vague and hard to think about in physical terms. Another reason for the existent confusion surrounding closures is, in my opinion, the way they are often taught: With overly technical, insufficient explanations and an overt focus on syntax. Do not get me wrong though — I think that the syntax is important (that’s why I will also teach it in this tutorial lol) and should be taught adequately. But, in my view, the best way to teach closures, so students experience the hotly desired AHAA💡😯!! moment, is to bombard them with good examples and elaborate explanations. Enough chit chat. Let us dive in!
Both functions accept two parameters as input and have one output, all of type Int. So far, so good here. But did you actually know, that you can insert a function type as a parameter to a function? This looks like this:
What is going on here? It might look intimidating if it is the first time you are seeing this, but I assure you it is pretty simple: We have a function called ‘doMath’, that accepts three parameters. The three parameters are a, b and operation. The types of these parameters are both Int for a and b, while the type for the third parameter, operation, is a function of type (Int, Int) -> Int. Do you see the resemblance to our first two functions?
In the source code of the ‘doMath’ function, we are conducting the operation (a,b). We can call the ‘doMath’ function in multiple ways:
On line 1 we are calling the ‘doMath’ function, we insert the numbers 2 and 4 for a and b. Notice how we insert the function addTwoNumbers for the parameter operation. This is only possible, because the type of the function addTwoNumbers matches with the type with which the operation parameter was set when we declared the function doMath. Both have a type of (Int, Int) -> Int. We can test this. Let us change the function addTwoNumbers slightly:
Now, let’s try to call doMath() again:
It also doesn’t work if we do this:
The types have to match. If the types match, we can do this:
If you are following along in playground, you may notice something that might confuse you: Why can’t we store the values that are returned by the functions addTwoNumbers() and multiplyTwoNumbers() when we call doMath()? For the case you do not know what I mean, consider this:
On lines 15 and 17, we call doMath() and store the result in constants. But the result is of type (), also called Void. The reason is, that doMath() doesn’t return anything. In fact, all functions that don’t have an explicit return type, return Void implicitly. If we want it to return something, we do the following:
Since doMath() has an output of type Int here, we can store the value in doMath() to a variable or constant. OK, but where do closures come into play? Have a look at this:
What we just did, was condense 4 lines of code to one single line. How did we do it? The steps are described here:
What’s the point of all of this? One great benefit of closures is, that we can keep code in the same place in a concise, clean way. Swift provides ways on how we can make the closure even more concise:
Let’s go through this in detail.
Step 1: Swift is able to infer types. Have a look at our doMath() function at the top. When we call it, Swift already knows what types it is made of. It knows that a and b are of type Int and that operation is of type (Int, Int) -> Int. That is why it would be redundant to state them again.
Step 2: If you have a return that is short enough so it fits in one line of code, you can omit the keyword ‘return’. Here is another example for this:
Step 3: Swift provides shorthand argument names for closures. In our example, our parameter a is substituted with $0, while b is replaced with $1. If we had more parameters, like c and d, we could add more shorthand argument names, like $2 and $3. I want you to put yourself in the shoes of Swift now: You know how the doMath() function looks like. You know its types, you know how many parameters it has. Why not leave out the argument list (what comes before the keyword in) a and b on line 25? If you have the information, that you will add two numbers, you already know what the argument list is. Well, if that’s the case, you might think, can we instead use a + b instead of $0 + $1?
That doesn’t work. It only works with the shorthand argument names ($0, $1 etc.) provided by Swift.
Step 4: If you provide a closure as a function’s final argument, you can pass it as a trailing closure. Notice the wording here: You can do it, but you don’t have to. This prerequisite for a trailing closure fits perfectly in our example: We are passing operation, which is a closure, as the final/last argument of the function doMath(). And that’s it. We have just created a trailing closure!
I hope, this short tutorial was of any help to you. Like I stated in the beginning, this tutorial just covers the basics of closures. In subsequent posts, I will talk about more advanced concepts of closures.