An introduction to closures in Swift iOS 29.08.2018

Closures are self-contained blocks of code that can be passed around and used throughout our application. We can think of the Int type as a type that stores an integer and a String type as a type that stores a string. In this context, a closure can be thought of as a type that contains a block of code. What this means is that we can assign closures to a variable, pass them as arguments to functions, and return them from a function.

A closure can be thought of as a type that contains a block of code.

Closures have the ability to capture and store references to any variable or constant from the context in which they were defined. This is known as closing over the variables or constants and, for the most part, Swift will handle the memory management for us. The only exception is in creating a strong reference cycle.

Let's look at the syntax used to define a closure in Swift:

{
    (parameters) -> return-type in
    statements
}

The syntax used to create a closure looks very similar to the syntax we use to create functions and, in Swift, global and nested functions are closures. The biggest difference in the format between closures and functions is the in keyword. The in keyword is used in place of curly brackets to separate the definition of the closure's parameter and return types from the body of the closure.

We will begin by creating a very simple closure that does not accept any arguments and does not return any value. All it does is print Hello World to the console. Let's look at the following code:

let clos1 = { () -> Void in
    print("Hello World")
}

There are many ways to use closures; in this example, all we want to do is execute it. We can execute the closure as follows:

clos1()

Let's look at another simple example. This closure will accept one string parameter named name, but will not return a value. Within the body of the closure, we will print out a greeting to the name passed into the closure through the name parameter. Here is the code for this second closure:

let clos2 = {
    (name: String) -> Void in
    print("Hello \(name)")
}

Let's look at how to pass our clos2 closure into a function. We will define a function that accepts our clos2 closure, as follows:

func testClosure(handler: (String) -> Void) {
    handler("Dasher")
}

We can execute the closure as follows:

testClosure(handler: clos2)

As the final piece to the closure puzzle, let's look at how to return a value from a closure. The following example shows this:

let clos3 = {
    (name: String) -> String in
    return "Hello \(name)"
}

We can now execute the clos3 closure just like the previous two closures, or pass the closure to a function like we did with the clos2 closure. The following example shows how to execute the clos3 closure:

var message = clos3("Buddy")

After this line of code is executed, the message variable will contain the Hello Buddy string.

Shorthand syntax for closures

We will look at a couple of ways to shorten the definition of closures. The first shorthand syntax for closures that we are going to look at is one of the most popular. This format is mainly used when we want to send a really small (usually one line) closure to a function.

Before we look at this shorthand syntax, we need to write a function that will accept a closure as a parameter:

func testFunction(num: Int, handler:() -> Void) {
    for _ in 0..<num {
        handler()
    }
}

Now let's create a closure and pass it to the testFunction() as follows:

let clos = {
    () -> Void in
    print("Hello from standard syntax")
}

testFunction(num: 5, handler: clos)

This code is very easy to read and understand; however, it does take five lines of code. Now let's look at how to shorten this code by writing the closure inline within the function call:

testFunction(num: 5,handler: {print("Hello from Shorthand closure")})

The ideal way to call the testFunction() with a closure, for both compactness and readability, would be as follows:

testFunction(num: 5) {
    print("Hello from Shorthand closure")
}

Having the closure as the final parameter allows us to leave off the label when calling the function. This example gives us both compact and readable code.

Let's look at how to use parameters with this shorthand syntax. We will begin by creating a new function that will accept a closure with a single parameter. We will name this function testFunction2. The following example shows what the new testFunction2 function does:

func testFunction2(num: Int, handler: (_ : String)->Void) {
    for _ in 0..<num {
        handler("Me")
    }
}

In testFunction2, we define the closure like this: (_ : String)->Void. This definition means that the closure accepts one parameter and does not return any value. Now let's look at how to use the same shorthand syntax to call this function:

testFunction2(num: 5){
    print("Hello from \($0)")
}

The difference between this closure definition and the previous one is $0. The $0 parameter is shorthand for the first parameter passed into the function. If we execute this code, it prints out the message Hello from Me five times.

Using the dollar sign ($) followed by a number with inline closures allows us to define the closure without having to create a parameter list in the definition. The number after the dollar sign defines the position of the parameter in the parameter list. Let's examine this format a bit more, because we are not limited to only using the dollar sign ($) and number shorthand format with inline closures. This shorthand syntax can also be used to shorten the closure definition by allowing us to leave the parameter names off. The following example demonstrates this:

let clos5: (String, String) -> Void = {
    print("\($0) \($1)")
}

We can access the parameters within the body of the closure using $0 and $1.

Let's look at how we would use the clos5 closure as follows:

clos5("Hello", "John")

If the entire closure body consists of only a single statement, then we can omit the return keyword, and the results of the statement will be returned. Let's look at an example of this:

let clos7 = {(first: Int, second: Int) -> Int in first + second }

Let's define an array to use:

let guests = ["Jon", "Kim", "Kailey", "Kara"]

Now that we have our guests array, let's add a closure that will print a greeting to each of the names in the array:

guests.map { name in
    print("Hello \(name)")
}

Since the map algorithm applies the closure to each item of the array, this example will print out a greeting for each name within the array. Using the shorthand syntax we could reduce the preceding example down to the following single line of code:

guests.map {print("Hello \($0)")}

Now, let's say that rather than printing the greeting to the console, we wanted to return a new array that contained the greetings. For this, we would return a String type from our closure, as shown in the following example:

var messages = guests.map {
    (name:String) -> String in
    return "Welcome \(name)"
}

When this code is executed, the messages array will contain a greeting to each of the names in the guests array, while the guests array will remain unchanged. We could access the greetings as follows:

for message in messages {
    print("\(message)")
}

We could assign the closure to a constant or variable and then pass in the closure

let greetGuest = {
    (name:String) -> Void in
    print("Hello guest named \(name)")
}

guests.map(greetGuest)

Escaping Closures

If a function passed around as a value will be preserved for later execution, rather than being called directly, it is a closure that captures and preserves its environment over time. That’s called an escaping closure. In some situations, the function’s type must be explicitly marked @escaping.

So, for example, this function is legal because it receives a function and calls it directly:

func funcCaller(f:() -> ()) { 
    f()
}

And this function is legal, even though it returns a function to be executed later, because it also creates that function internally. The function that it returns is an escaping closure, but the type of the function’s returned value does not have to be marked as @escaping:

func funcMaker() -> () -> () { 
    return { print("hello world") }
}

But this function is illegal. It receives a function as a parameter and returns that function to be executed later:

func funcPasser(f:() -> ()) -> () -> () { 
    // compile e rror
    return f 
}

The solution is to mark the type of the incoming parameter f as @escaping, and the compiler will prompt you to do so:

func funcPasser(f:@escaping () -> ()) -> () -> () { 
    return f
}

A secondary feature of escaping closures is that, whenever you refer to a property or method of self within the function body, the compiler will insist that you say self explicitly. That’s because such a reference captures self, and the compiler wants you to acknowledge this fact by saying self.

An escaping closure outlives the function it was passed to.

In this sense there are two kinds of closures:

  • An escaping closure is a closure that’s called after the function it was passed to returns. In other words, it outlives the function it was passed to.
  • A non-escaping closure is a closure that’s called within the function it was passed into, i.e. before it returns.

A good example of an escaping closure is a completion handler. It’s executed in the future, when a lengthy task completes, so it outlives the function it was created in. Another example is asynchronous programming: a closure that’s executed asynchronously always escapes its original context.

So, if a closure is stored somewhere (e.g. in a property) to be called later, it is said to be escaping. Conversely, closures that never leave a function’s local scope are non-escaping. With escaping closures, the compiler forces us to be explicit about using self in closure expressions, because unintentionally capturing self strongly is one of the most frequent causes of reference cycles. A non-escaping closure can’t create a permanent reference cycle because it’s automatically destroyed when the function it’s defined in returns. Closure arguments are non-escaping by default. If you want to store a closure for later use, you need to mark the closure argument as @escaping.