All You Need to Know About Swift’s Closure
Closures are self-contained blocks of functionality that can be passed around and used in your code.
Before you read the article, I assume that you have a basic Swift knowledge,
otherwise, you can learn from the official documentation here: https://swift.org/
Let’s start with its definition, according to Apple docs, a closure is defined as
The definition from Apple is pretty straightforward as we can conclude that closure is just a block of code, or if you would rather call a function, that can be assigned to a variable and reused all over your code, seems handy right?
If an analogy could help you understand more about the closure, consider this one
- Let's say Gon and Killua are eating in a ramen restaurant in Japan
- Gon wants to add some chili to his ramen, but the seasoning box is too far from him, but it is aside from Killua ( because they are practicing social distancing as well ) since it is impolite to talk while eating, Gon decides to write an instruction on a paper to Killua to hand him the seasoning box
- Gon write down
HandleMeTheSeasoningBox()
on the paper then passed the paper to Killua - Killua reads the paper then handed the seasoning box to Gon.
In this case, both Gon and Killua are objects from the Person class, the paper that Gon passed to Killua is a variable, the written HandleMeTheSeasoningBox()
instruction on the paper passed to Killua is called a closure ( since it is a function and it is assigned to a variable ).
Now it kinda makes sense right? Let’s jump to the code :D
In the above code, what we did is
- we declare a variable named
giveBoxToMe
, which contains a function thus make itself a closure - on line 8 we call the variable, notice that we add parentheses
()
to the variable, because we are going to “execute” the variable, which itself is a function to be executed - if you check the variable type ( ⌘ + click + jump to definition ), it will show that
giveBoxToMe
has a type of()->()
( which is proven that closure is just a function, but it stored in a variable hence the variable itself then will have a type of function :D )
bear in mind that every function has type that follows this format
()->()
, if the function has a string parameter and return void, then its type would be(String)->()
, if it does not have parameter but return an Integer, it would be()->(Int)
, and so on.
“Wait, is that all about the so-called closure?”
Well, that is the core of the closure concept, in this post I will explain it as deeply as I can since I found it so useful when developing iOS apps, so bear with me! :D
As I stated above, a function could have many types, whether it receives a parameter or it returns something, then we know that closure is also a function, hence it could also receive parameters and return something too, consider this example
In the code above, we :
- Define another closure, this time our closure takes a string parameter and return nothing
- on line 1, after the
=
we see something like
{ name in, the name is a closure parameter referencing to the outside parameter passed to the closure ( in this case, the string ) to then be used inside the closure body, that’s why it is called closure parameter. - on line 5, we called our closure like before, but this time we inject a String as it is needed by our new closure, notice how our closure has no named parameters and we could directly pass a string on it.
Let’s Recap What we know so far :
- Closure is just a function.
- Closure could have many types, whether it is a function that receives a parameter and returns something or just a plain void function
- We could pass an argument to a closure, and we could use that passed argument inside the closure body
Closure Type Inference
Swift is a strongly typed language, the meaning of it is that every variable has a type. even if you don’t specify it explicitly, Swift will do it for you, how awesome it that!. Consider this example
let isOpen = true
What is the type of isOpen variable? Swift will infer the type based on the context of the value given to the variable, the true value is a literal value for a boolean, hence the constant isOpen has a boolean type
You can make use of it in Closure! we can leave parts of our closure expression for Swift to infer what type is it. Consider another example :
What we did on the above code :
- We define a variable that is a type of String array
- We then sort them alphabetically by using the function
sorted(by:)
- Finally, we print out the result
When we see inside the sorted(by:)
function, we know that it accepts a closure as its parameter, hence we why are only passing a <
?
If we expand the <
, then the function would be like this
It then could be simplified further into
We could go even further like
And then we used shorthands arguments
We can omit the parentheses
Then finally we can just use the <
as the parameter
Very clean right? here’s the detail explanation of each gists :
- The first example uses the complete closure syntax, including two parameter names
a
andb
of typeString
, and a return type ofBool
. Notice that it also has thein
andreturn
keywords. - The second example omits the closure parameter types because they can be inferred from context. Since we’re sorting an array of strings, those two parameters are inferred as
String
. (When omitting the types, you can also omit the parentheses) - The third example omits
return
, because the closure is only one line of code. When that’s the case, Swift figures out that you want to return a value, even if you don’t includereturn
. - The fourth example uses the
$0
and$1
shorthands to reference the first and second parameters of the closure. The parameters are there, they just don’t have names! Again, with inferredString
types. - The fifth example uses trailing closure syntax. When a closure is the last (or only) parameter of a function, you can write the closure outside the function’s parentheses.
- And the last example uses the smaller-than operator
<
as the closure. In Swift, operators are top-level functions. It’s type is(lhs: (), rhs: ()) -> Bool
, and that fits the type ofsorted(by:)
perfectly!
Let’s Recap What we know so far :
- Swift uses type inference when you do not specify a type of a variable or a function
- You can make use of Swift’s type inference to simplify your closure
Closure Capturing in Swift
There is one important concept of Closure which I have not explained so far, it is capturing.
Swift, just like any other programming language, has something called scope in it. Every variable and function in Swift has a scope. Scope determines whether you can access a particular variable or function. For example, if the function you wanted to execute is not in the same scope as the calling code, then you cannot execute it. For examples :
- A property defined in a class is part of the class scope, it means that anywhere within the class have access to the property ( thus able to set or get the value )
- A variable defined in a function is part of the local function scope, it means that anywhere within the function has to access to the property, and vice versa. ( Anywhere outside the function could not access the variable )
Consider this example :
What we did :
- We define a function named
calculateCircleArea
that takes a double parameter and returns a double value. - Inside the function, we declare a variable named π and assigned a constant Math PI value to it
- Then we made a closure named
area
that returns a calculation of π and the radius retrieved from the parameter - Then we call the
calculateCircleArea
function with radius parameter 10 and assign it to the result variable - Eventually, we print out the result
Wait.. what?
Notice that inline 5 the closure we made have access to the π variable even it is not defined inside the closure scope, that’s because of capturing
A closure has access to the scope it is defined, i.e. π can be retrieved within the closure, even though they were declared in the local function scope
There are a few things to note :
- A closure only captures variables etc. that are actually used in the closure. When a variable isn’t accessed in the closure, it isn’t captured.
- A closure can capture variables and constants. You can technically also capture other closures because they’re variables or constants too, and properties, because a property belongs to an object that’s assigned to a variable or constant. And you can of course call functions in a closure, but they aren’t captured, because functions aren’t variables (or “stored”).
- Capturing only works one way. The closure captures the scope it is defined in, but code “outside” a closure doesn’t have access to values “inside” the closure.
Capturing values with closures can lead to all sorts of fun with strong reference cycles and memory leaks, but since it is a big topic, let’s leave that on another post. 👯👯
Closure on Action
So that we learned about Closure, from its syntax to its ability to capture its surrounding scope, let’s see how we can use this powerful feature in Swift.
From my code experience, I use closure for 2 scenarios :
- I have a ViewController that has nested children, and on one of those children I need to access the root ViewController’s property and do some logic to it, hence I must create a closure on my root ViewController and passed the closure down to the bottom child who needed it, it may not be the cleanest way of doing this but closure is one way to get the job done.
- As a completion handler, we could use closure to do something after a lengthy task, for example, downloading data from the internet, then after the data has been downloaded, we could just show a popup telling the user that their download is already completed.
Let’s Recap What we know so far :
- Closure can access its surrounding scope by capturing it
- Closure can be passed around your code
- Closure can be used as a completion handler
That’s all folks.
Happy Coding