This tutorial dives into Swift closures. Closures are blocks of code that you can pass around in your code, as if you assign a function to a variable. Mastering closures is a crucial aspect of learning iOS development.
If you had a tough time understanding optionals, then you’ll probably find the prospect of mastering Swift’s closures even more terrifying! Don’t worry though, they are more harmless than they look. And closures are useful, too!
Here’s what you’ll learn:
Ready? Let’s go.
You’ll have to forgive me for the cheesy tropical island image at the top of this tutorial. For starters, it’s really hard to find a visual representation of an abstract concept like a closure. On top of that, finally “getting” closures is kinda like arriving at a tropical destination. And by the way, you’ll definitely need a happy place to endure the journey towards the mastery of closures… ;-)
Let’s look at Apple’s official description of a closure:
Closures are self-contained blocks of functionality that can be passed around and used in your code.
Said differently, a closure is a block of code that you can assign to a variable. You can then pass it around in your code, for instance to another function. That function then calls the closure and executes its code, as if the closure is an ordinary function.
As you know, variables store information in your Swift code, and functions can execute tasks. With closures, you put a function’s code in a variable, pass it around, and execute its code somewhere else.
Let’s look at an analogy:
Makes sense, right? Let’s write a closure in Swift!
[sandbox]
let birthday = {
print(“Happy birthday!”)
}
birthday()
[/sandbox]
Here’s what happens in that code:
birthday
. The closure is the stuff between the squiggly brackets {
and }
. See how it’s similar to a function declaration? Note that the closure is assigned to birthday
with the assignment operator =
.birthday()
, the name of the constant birthday
with parentheses ()
. This is similar to calling a function.birthday()
, which will execute the closure, and print out Happy birthday! with the print() function.At this point, the type of birthday
, and the type of the closure, is () -> ()
. More on closure types later.
Next, let’s add a parameter to the closure. Parameters are input values for functions and closures. Just like functions, closures can have parameters.
[sandbox]
let birthday:(String) -> () = { name in
print(“Happy birthday, \(name)!”)
}
birthday(“Arthur”)
[/sandbox]
Alright, that’s getting more complicated. Let’s deconstruct that code bit by bit. Here’s what happens:
birthday
, and call the closure on the last line.String
. This parameter is declared as part of the closure type (String) -> ()
. name
within the closure. When calling the closure, you provide a value for the parameter.What do you make of that? In essence, there are three things that matter here:
(String) -> ()
{ name in ··· }
birthday(···)
The parameters of a closure aren’t named, unlike Swift functions. When you declare a closure you can specify the types of parameters it has, such as String
in the above example, but you don’t specify a parameter name – only the type.
In the closure expression, the { name in ···
part, you assign a local variable name
to the first parameter of the closure. This gives the parameter a name within the closure. You could have named it anything you wanted.
In fact, you could have left out a variable name, and used the $0
shorthand! Like this:
let birthday:(String) -> () = {
print("Happy birthday, \($0)!")
}
In the above code, the closure birthday
has one parameter. Inside the closure, the shorthand $0
is used to reference the value of that first parameter.
When you call a closure, you don’t include a name for any parameters it has. This is different from a function, which usually has named parameters (called argument labels). Here’s how that works for closures:
birthday("Arthur")
// Output: Happy birthday, Arthur!
See how that works? We’re calling the closure birthday(_:)
and provide one parameter "Arthur"
of type String
. This parameter doesn’t have a name, just a value. As we’ve discussed before, the value gets a name within the closure.
Here’s what you’ve learned so far:
Let’s take a closer look at the type of a closure, in the next section.
Every closure has a type, just like any other variable, constant, property, etcetera.
Conceptually, there’s no difference between a variable that’s declared with type Int
and a variable declared as type (Int) -> ()
. The former is has type integer, and the latter is a closure type. The types themselves are different, but they both are types.
You declare a closure with one parameter like this:
let birthday:(String) -> () = { (name:String) -> () in
···
}
That looks complicated, right? Here’s how it works:
{
and }
is the closure expression. The closure assigned to the constant birthday
with the let
keyword.(String) -> ()
is the closure type. This closure has one parameter of type String
and it returns ()
, which means “nothing”.(name:String) -> ()
is the same closure type, except that it names the first parameter of the closure as name
. It’s part of the closure expression.in
keyword separates the closure parameters and the closure body. Before in
comes the closure type, and after in
you write the code of the closure.Let’s deconstruct closure types to better understand how closures work. In it’s most essential form, this is the syntax for a closure:
let closureName:(parameter types) -> return type = { (parameter name:parameter type) -> return type in
···
}
What’s going on? First off, we’ve got 3 important parts here:
let closureName
(parameter types) -> return type
{ (parameter name:parameter type) -> return type in ··· }
The constant that we assign the closure to is simple. From now on, we can pass that closure around using its constant (or variable) name.
The syntax for the closure type consists of parameter types and a return type. Just like a function, a closure can have parameters and return a value. The syntax to declare this type consists of types wrapped in parentheses, followed by a single arrow, followed by another type. A few examples:
(Int, Int) -> Double
, so 2 integer parameters, and returns a Double
value() -> Int
, no parameters, returns an integer(String) -> String
, takes a string, returns a stringOK, let’s move on. What about the closure expression? Here it is again:
{ (parameter name:parameter type) -> return type in
···
}
If you look closely, you’ll see that the closure type is repeated here. We’ve started with an opening squiggly bracket {
, then declare the closure type again, followed by in
, followed by some Swift code, followed by a closing squiggly bracket }
.
The closure type is declared differently here, though! It now includes names for the parameters. Let’s look at a few examples; the same as before:
(Int, Int) -> Double
becomes { (width: Int, height: Int) -> Double in ···
() -> Int
becomes { () -> Int in ···
(nothing changes b/c no parameters)(String) -> String
becomes { (text: String) -> String in ···
You can name these parameters anything you want, but you will need to give them names. These constants can be used “locally” inside the closure, just like parameters within a function.
What about in
? It’s easiest to think of “in” as in normal English language. So, we’ve got a closure, and we say: “We’ve got parameters X, Y, Z in this block of code; which is the closure.”
The above example uses the complete closure syntax. In the next section, we’ll discuss which parts of that syntax can be “inferred” and left out.
A closure with no parameters and no return type, has the following closure type:
() -> ()
The above expression consists of two empty tuples ()
and a single arrow ->
. The first tuple is the input for the closure, and the second tuple is the output for the closure.
A tuple is an ordered list of items, like (a, b, c)
. It’s comma-separated and wrapped in parentheses. An example in Swift: let flight = (airport: "LAX", airplane: 747)
. Read more about tuples here: Tuples In Swift Explained
You can also write Void
for the closure’s return type, like this:
() -> Void
In Swift, Void
means “nothing”. When your function returns Void
, it does not return anything. Not even nil
or an empty string! As described in the Apple Developer Documentation, Void
is an alias of an empty tuple ()
. When a function or closure doesn’t return anything, its return type is Void
.
Let’s look at one last example. The following function has two parameters, and returns a value:
[sandbox]
let greeting:(String, String) -> String = { (time:String, name:String) -> String in
return “Good \(time), \(name)!”
}
let text = greeting(“morning”, “Arthur”)
print(text)
[/sandbox]
The closure greeting
has two parameters, both of type String
. The closure returns a value of type String
. The type of greeting
is defined explicitly, and so are the closure parameters in the closure expression itself.
When the closure is called, it is provided two arguments of type String
, and its return value is assigned to text
, and then printed out.
See how there are two parameters of type String
, and a return type of type String
? The closure type (String, String) -> String
defines the input and output for the closure, and the expression (time:String, name:String)
gives the input parameters local variable names.
As you’ll find out in the next chapter, you can write the exact same closure expression also like this:
let greeting:(String, String) -> String = { "Good \($0), \($1)!" }
let text = greeting("morning", "Arthur")
print(text)
Let’s summarize:
() -> Void
, with no input parameters and no return valuelet greeting:(String) -> Void ···
··· { (name:String) -> Void in ···
Awesome! Let’s move on.
Can you use optionals with closures? Yes! Any closure parameter type or return type can be optional, like this: (String?, Int) -> Int?
. You can also make the closure itself optional, which means that the variable, constant or property that references the closure can be nil
. You do this by wrapping the entire closure type declaration in parentheses, followed by a ?
. Like this: let birthday:((String) -> ())? = ···
. The constant birthday
can now be nil
or contain a closure.
Swift has a super useful feature called type inference. It works like this: When you don’t explicitly specify the type of a variable, Swift can figure out on its own what the type of that variable is. It does so based on the context of your code.
Here’s an example:
let age = 104
What’s the type of age
? Swift will infer the type of age
based on the context of the above code. That 104
is a literal value for an integer number, so the constant age
has the type Int
. Swift figures this out on its own, without your needing to provide an explicit type. Neat!
Important: Swift is a strong-typed programming language. That means that every value has a type, even if it is inferred! Never mistake type inference for “this value has no type”. A value always has a type, you just don’t declare it explicitly. Type inference can lead to confusion. Make sure you always know the types of your variables!
Type inference and closures go hand-in-hand. As a result, you can leave out many parts of a closure expression. Swift will (usually) infer those parts on its own.
Let’s look at an example:
[sandbox]
let names = [“Zaphod”, “Slartibartfast”, “Trillian”, “Ford”, “Arthur”, “Marvin”]
let sortedNames = names.sorted(by: <)
print(sortedNames)
[/sandbox]
In the above code you’re creating an array with names, then sorting them alphabetically by calling the function sorted(by:)
, then assigning the result to sortedNames
, and then printing out the array of sorted names.
The key part of this example is names.sorted(by: <)
. That first parameter by:
takes a closure. You can provide a closure that’s used to sort the array. We’re providing just <
…
Now… get a load of this. First, when we expand <
it becomes this:
names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 < s2
})
Then, that’s the same as…
names.sorted(by: { s1, s2 in return s1 < s2 } )
And it’s also exactly the same as…
names.sorted(by: { s1, s2 in s1 < s2 } )
And you can also write that like…
names.sorted(by: { $0 < $1 } )
With trailing closure syntax, you can even write it as:
names.sorted { $0 < $1 }
And finally, you can also just use the <
operator as a function:
names.sorted(by: <)
Neat, right? The functionality of these different syntaxes is exactly the same! All thanks to type inference and closure expressions. Here’s the gist of every closure expression above, starting with the most expanded one:
s1
and s2
of type String
, and a return type of Bool
. It also uses the in
and return
keywords.String
. (When omitting the types, you can also omit the parentheses. This s1, s2 in
type of syntax is common.)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 include return
.$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 inferred String
types.<
as the closure. In Swift, operators are top-level functions. Its type is (lhs: (), rhs: ()) -> Bool
, and that fits the type of sorted(by:)
perfectly!It’s hopefully now that you’re starting to see the power of closures, their expressivity, and how the Swift programming language enables you to write elegant code. Closures are also important for SwiftUI’s syntax.
It helps to practice Swift coding and working with closures as often as you can. Practice makes perfect! Don’t wait to learn about closures until you’ve ran into trouble. Set aside some of your time to discover and grasp new programming concepts and best practices.
Personally, I like the sorted { $0 < $1 }
expression most. Remember that it’s not always smartest to choose the most concise programming approach. In fact, it helps to be more descriptive than clever!
By the way, closures can get crazy really quickly. Check this out:
[sandbox]
let squared = { $0 * $0 }(12)
print(squared)
[/sandbox]
The closure { $0 * $0 }
is immediately called with ()
, right after defining it, effectively assigning the result of 12 * 12
to squared
. Executing a closure directly after it is defined is the basis for lazy computed properties.
So, to summarize:
Trailing closure syntax is super helpful. Here’s how it works: When a closure is the last (or only) parameter of a function, you can write the closure outside the function’s parentheses, and omit that parameter’s label. So, the code sorted(by: { $0 < $1 } )
becomes sorted { $0 < $1 }
. See how the label and parentheses have changed?
Seeing a closure as a function you can assign to a variable doesn’t do the concept of closures the justice it deserves. When I explained closures like that, I left out an important part: capturing.
The name “closure” comes from “enclosing”, and when we stretch it, it comes from the functional programming concept of “closing over”. In Swift, a closure captures variables and constants from its surrounding scope.
The words “closing over”, “capturing” and “enclosing” all mean the same thing here.
Every variable, function and closure in Swift has a scope. Scope determines where you can access a particular variable, function or closure. If a variable, function or closure isn’t in a scope, you can’t access it. Scope is sometimes called “context”.
Compare it to the “context” in which your house keys exist. When you’re outside the house, and your keys are inside the house, they’re not in the same context, and you can’t unlock the front door. When both you and the house keys are outside the house, you’re in the same context, and you can unlock the door.
Your code has global scopes and local scopes. Some examples:
Want to learn more about scope and context? Check out this tutorial: Scope & Context Explained In Swift
Let’s look at an example of how a closure captures its surrounding scope.
[sandbox]
let name = “Zaphod”
let greeting = {
print(“Don’t panic, \(name)!”)
}
greeting()
[/sandbox]
Here’s what happens in the code:
name
is defined with value "Zaphod"
of type String
.greeting
. The closure prints out some text.greeting()
.In the example, the closure closes over the local variable name
. It encapsulates variables that are available in the scope that the closure is defined in. As a result, we can access name
even though it’s not declared locally within the closure!
A smart reader will now point out that name
is accessible in the closure’s body because in a REPL environment, such as a sandbox or Swift Playground, top-level variables are part of the global scope. Yes, that’s right! You can access them anywhere. Please check the next example.
Let’s look at a more complex example:
[sandbox]
func addScore(_ points: Int) -> Int
{
let score = 42
let calculate = {
return score + points
}
return calculate()
}
let value = addScore(11)
print(value)
[/sandbox]
What happens here?
addScore(_:)
is defined. It will return a score based on the parameter points
and the “previous” score of 42.calculate
is defined. It simply adds score
and points
and returns the result. The function calls the closure with calculate()
.addScore(_:)
is called, assigned to value
, and printed out.The closure calculate
captures both score
and points
! Neither of those variables are declared locally within the closure, yet the closure can get their values. That’s because of capturing.
This color diagram explains it better visually:
See how there are different scopes? And see how the closure scope has access to score
and points
, that are part of the local function scope?
There are a few things worth noting here:
Capturing values with closures can lead to all sorts of fun with strong reference cycles and memory leaks. And that’s where the capture list comes in.
First things first. When a closure captures a value, it automatically creates a strong reference to that value. When Bob has a strong reference to Alice, then Alice isn’t removed from memory until Bob is removed from memory.
That usually goes OK. But what if Alice has a strong reference back to Bob? Then both Bob and Alice won’t be removed from memory, because they’re holding on to each other. Bob can’t be removed because Alice is holding onto him, and Alice can’t be removed because Bob is holding onto her.
This is called a strong reference cycle and it causes a memory leak. Imagine a hundred Bob’s and Alice’s taking up 10 MB each in memory, and you see what the problem is: less memory for other apps, and no way to remove it.
Memory in iOS is managed with a concept called Automatic Reference Counting. It’s different than garbage collection. Most of memory management with ARC is done for you, but you have’ll to avoid strong reference cycles. Memory management is a hairy subject, so we’ll leave that for another article.
You can break a strong reference cycle with a capture list. Just like you can mark a class’ property as weak
, you can mark captured values in a closure as a weak
or unowned
reference. Their default is strong.
Here’s an example:
[sandbox]
class Database {
var data = 0
}
let database = Database()
database.data = 11010101
let calculate = { [weak database] multiplier in
return database!.data * multiplier
}
let result = calculate(2)
print(result)
[/sandbox]
Here’s what happens in the code:
Database
. It has one property data
. It’s a fictional class, so imagine that it’s super memory intensive…Database
, assign it to database
, and set its data
property to some integer value.calculate
. The closure takes one argument multiplier
, and it captures database
. Within the closure, the data
is simply multiplied by multiplier
.2
and its result is assigned to result
.The key part is the capture list, here:
··· { [weak database] ···
A capture list is a comma-separated list of variable names, prepended with weak
or unowned
, and wrapped in square brackets. Some examples:
[weak self]
[unowned navigationController]
[unowned self, weak database]
You use a capture list to specify that a particular captured value needs to be referenced as weak
or unowned
. Both weak
and unowned
break the strong reference cycle, so the closure won’t hold on to the captured object.
Here’s what they mean:
weak
keyword indicates that the captured value can become nil
unowned
keyword indicates that the captured value never becomes nil
Both weak
and unowned
are the opposite of a strong reference, with the difference that weak
indicates a variable that can become nil
at some point.
You typically use unowned
when the closure and the captured value will always refer to each other, and will always be deallocated at the same time. An example is [unowned self]
in a view controller, where the closure will never outlive the view controller.
You typically use weak
when the captured value at some point becomes nil
. This can happen when the closure outlives the context it was created in, such as a view controller that’s deallocated before a lengthy task is completed. As a result, the captured value is an optional.
The concept of capturing, capture lists and memory management is tricky. It’s not that complicated, but I think it’s just hard to visualize such an abstract concept. In practical iOS development, the most common capture list is [weak self]
or [unowned self]
.
As you’re reading this, just take note of the concepts and refer back to them whenever you get into trouble with memory management. Xcode will tell you when you’re using self
in a closure, and if you do, that might be a good time to read up on the exact inner workings of capture lists.
Here’s what you learned so far:
weak
and unowned
Let’s move on to the next and last section about completion handlers!
So what do you actually use closures for? They are incredibly powerful tools, but if you can’t put them to use, they won’t do you much good.
A common application of a closure is the completion handler. It works roughly like this:
Here’s an example:
let task = session.dataTask(with: "http://example.com/api", completionHandler: { data, response, error in
// Do something...
})
In the above code we’re making a networking request to download some data and do something with that data when the request is completed.
That may not seem shocking, so here’s the kicker:
It’s like time travel! You can code as if there’s no passing of time between starting the request and its completion. Instead of waiting for the lengthy task to complete, we can just provide a bit of code that’s executed when the task completes, while we’re coding the networking request in the here-and-now!
When you pass a closure as an argument for a function, and when that closure outlives the function it was passed to, it is said to be an escaping closure. Since Swift 3, closures are non-escaping by default. When you declare a function that takes a closure, and when that closure is escaping, its parameter has to be marked with @escaping
. Read more about @escaping here.
Check out this diagram:
Here’s what happens:
The key part is this: we can code the completion handler at the same time we’re starting the lengthy task!
Imagine you want to use the data of a networking request to display an image:
let imageView = UIImageView()
HTTP.request("http://imgur.com/kittens", completionHandler: { data in
imageView.image = data
})
See what happens here? You’re defining an image view, starting the networking request, and providing a completion handler. The completion handler is executed when the lengthy task is completed.
The closure has captured a reference to imageView
, so you can set the image data when the lengthy task is completed. This happens in the future, but we’re still in the present! You can code it as if the lengthy task executes instantly.
Let’s compare that to the target-action pattern, another common approach to invoke functions at a later point in time.
func download()
{
let imageView = UIImageView()
Webservice.request("http://imgur.com/kittens", target: self, action: #selector(onDownloadComplete(_:)))
}
func onDownloadComplete(_ data: UIImage)
{
// Oh no! How do I get to the `imageView` from here?
}
See how that’s different?
Even though you can use target-action to determine what happens when a lengthy task completes, you don’t have the benefits of capturing, so you can’t respond to the completion in the here-and-now.
The smart reader now points out that you can make imageView
an instance property, so you can access it in onDownloadComplete(_:)
. Yes, that’s a good alternative! But what if you need access to multiple variables? You don’t want to add unnecessary clutter. At best, the completion handler is in a different function, in a different place.
OK, before we call it quits, let’s look at one last bit of magic with closures. Consider the following code:
[sandbox]
func lengthyTask(completionHandler: (Int) -> Int)
{
let result = completionHandler(42)
print(result)
}
lengthyTask(completionHandler: { number in
print(number)
return 101
})
[/sandbox]
Try it! What’s happening there?
lengthyTask(completionHandler:)
. When that function is executed, the completion handler is executed with one argument 42
. The completion handler also returns a result, which is printed out.lengthyTask(completionHandler:)
is called, and provided with a closure. The closure has one argument, as defined earlier, which is printed out with print(number)
, and it also returns a value 101
.As a result, the first print()
will print out 101
, and the second print()
will print out 42
. Wait a minute…
Is the closure providing a value back to the function that calls the closure? YES! It’s magical…
Because the closure is executed in lengthyTask
, but defined earlier, you can return values from your closure just like any other function. The return value ends up with whomever called the closure!
And that, dear developer, is how closures work. Pfew! Quite fascinating and thrilling, right? It’s mind-boggling!
Here’s what you learned:
One last thing… if closure syntax ever has you beat, check out fuckingclosuresyntax.com for the skinny.
Want to learn more? Check out these resources: