Introduction to Swift Closures in 2023

In this article we’ll explore closures, an alternative way of grouping code. Know what closures are, its difference to functions, and when best to use it.

Continue Reading…

Written by

Iñaki Narciso

Published on

11 May 2023

Written by Iñaki Narciso
Written for Swift 5.8 using Xcode 14.3


If you’ve read the Beginner’s Guide to Swift Functions article, you’d know that functions are one way to group code that executes together.

In this article, we’ll explore closures, an alternative way of grouping code. We’ll discuss what closures are, its difference to functions, and when best to use it.

Before we begin, I would recommend giving the Beginner’s Guide to Swift Functions a few minutes to read. It tackles concepts such as the function signature, function body, parameter arguments, and return type in greater detail. These concepts are discussed here as well but in lesser detail.

Swift Closures

Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages. – Swift Programming Language Guide (Swift 5.8, 2023)

A Closure in Swift is basically a block of code that are executed together without creating a named function.

Closure Expression Syntax

{ (<parameters>) -> <#return type> in

}

The closure expression syntax looks like a function that doesn’t have a name in its signature. If you recall from the Beginner’s Guide to Swift Functions, a function body is written inside the curly braces {}, and closures start with just that, curly braces {}.

There are subtle differences though, closures are not declared using the func keyword, and they don’t have names in their signatures.

Closure Signature

Similar to functions, a closure can accept a list of parameters, and return a value. Closure signatures look like these:

// A closure that accepts two String parameters
// and returns a Boolean
(String, String) -> Bool

// A closure that accepts a number and returns a number
(Int) -> Int

// Example of a closure that accepts no parameter,
// and doesn't return a value.
() -> Void

It’s hard to determine what a closure does based on its signature alone since it doesn’t have a name that tells us what it does, but its signature does tell what parameters it accepts as well as its return type.

The way that we can infer what a closure does is by reading the code inside its body.

Following the closure expression syntax above, let’s implement some examples of how to use them based on their signatures.

// Example 1: (String, String) -> Bool closure

{ (string1: String, string2: String) -> Bool in
    // we can implement this closure to determine
    // if the characters from the first string (string1)
    // appears first in the alphabet
    // than the characters from string2
    return string1 < string2
}
// As you might've guessed a use-case for the closure
// above is for sorting an array of strings
// alphabetically in ascending order
// If we want to reverse this behaviour, like sorting
// by descending order, we can change its operator
// from < (less than) to > (greater than).


// Example 2: (Int) - Int closure
// Let's define the (Int) -> Int closure:
{ (number: Int) -> Int in
    // Let's imagine that we're creating a closure that
    // squares the input number and returns it
    return pow(number, 2)
}


// Example 3: () -> Void closure
// For this closure, we'll print a simple statement!
{ in
    print("Closure is executed")
}

The examples above show you how to implement closures but with a single line of code. Since closures are also a way to group code, you can write multiple lines of code inside the closure as long as you satisfy its return type.

Walkthrough of the Syntax

A closure starts and end with curly braces {}. When defining a closure expression, you need to define the parameters and return type right after the opening brace { and end it with the keyword in.

The in keyword precedes the group of code that you’ll want to execute inside the closure. You can have a single line of code as shown in the examples above, or you can write multiple lines of code depending on your needs. What’s important is that you’ll return a value that matches the return type of the closure, unless the closure expects to return a Void where a return statement is unnecessary.

Using Closures

Calling functions are easy since functions have names, but closures don’t have names, so how do we call them?

Closures are typically used alongside functions. An example of this would be the Array.sorted(by:) method. The sorted(by:) function takes another function as a parameter, that’ll determine if a values are to be sorted in ascending or descending manner.

If you remember from the closure examples above, we have implemented a closure that’ll determine if the first string’s characters appear first before the characters from the second string. We can use that closure to sort an array of strings in alphabetical order.

let fruits = ["Banana", "Coconut", "Apple"]

var sortedFruits = fruits.sorted(by: { (string1: String, string2: String) -> Bool in
    return string1 < string2
})

// sortedFruits will now have:
// ["Apple", "Banana", "Coconut"]

Here we’ve managed to use a closure to help the sorted(by:) function implement its sorting behaviour.

However, as you’ve might notice, writing closures in full closure expression is tedious. Could we make this code shorter?

Yes, we can!

Inferring Type From Context

A closure’s type can be inferred from the function signature that calls it.

Expanding from the Array.sorted(by:) example above, the function signature of the sorted(by:) function when specialized to an Array of Strings will be:

func sorted(by: (String, String) -> Bool)) -> [String]

With that, the compiler can infer that any closures passed into the sorted(by:) function is a (String, String) -> Bool closure.

When we use type inference we call lessen the example code above into:

let fruits = ["Banana", "Coconut", "Apple"]

// before –  written in complete closure expression
var sortedFruits = fruits.sorted(by: { (string1: String, string2: String) -> Bool in
    return string1 < string2
})

// after – using type inference
var sortedFruits = fruits.sorted(by: { string1, string2 in
    return string1 < string2
})

In the example using type inference, both string1 and string2 no longer requires an explicit declaration of their type since the compiler infers both of them are String types by looking at the sorted(by:) function signature.

The example using type inference no longer declares the return type since it was also inferred by the compiler by looking at the sorted(by:) function signature.

Implicit Returns from Single-Expression Closures

The return keyword can be explicit when a closure only contains a single expression (or a one-line code).

// before – using type inference
var sortedFruits = fruits.sorted(by: { string1, string2 in
    return string1 < string2
})

// after - using implicit returns
var sortedFruits = fruits.sorted(by: { s1, s2 in s1 < s2})

Shorthand Argument Names

Inline closures as the one we’ve been using so far have shorthand argument names: $0, $1, and so on. The shorthand name refers to each closure parameter argument starting with $0.

So for our example we used a (String, String) -> Bool closure for the sorted(by:) function, and we’ve name string1 and string2 or s1 and s2 as the argument names for the closure parameters from our previous examples.

Instead of using string1 or s1, we can instead use the shorthand name $0 to refer to the first string parameter, and $1 to refer to the second string parameter that we named string2, or s2 from the previous examples.

// before - using implicit returns
var sortedFruits = fruits.sorted(by: { s1, s2 in s1 < s2 })

// after - shorthand argument names
var sortedFruits = fruits.sorted(by: { $0 < S1 })

Trailing Closures

So far we’ve been passing inline clossures as parameters, but in Swift you can write a closure as a trailing closure.

// before - shorthand argument names
var sortedFruits = fruits.sorted(by: { $0 < $1 })

// after - trailing closures
var sortedFruits = fruits.sorted { $0 < $1 }

A trailing closure can only be written if it is the function’s last argument. Though, you can write multiple trailing closures as what you may have observed on some SwiftUI views, you’ll only need to write the argument label for the succeeding trailing closures and not on the first one. An example to this would be a Button. A Button‘s init function accepts two functions as parameters: action : () -> Void and label: () -> Label.

// written in multiple trailing closures
Button {
    print("Button tapped")
} label: {
    Text("Tap me!")
}

// written in full closure expression syntax 
// w/ type inference
Button(
    action: { in
        print("Button tapped")
    },
    label: { in
        Text("Tap me!")
    }
)

If you’ve been working on apps yourself, you may have worked on closures with your code. Closures are really helpful especially when used with functions.

We’ve still have so much more to explore using closures such as @escaping closures, capturing values, and memory management, but these are discussions on best left on an article of their own.

That’s all for now! Cheers!


Got questions? Head over to our community forum where students and experts can help you!

Table of contents
    0 Shares
    Share
    Tweet
    Pin
    Share
    Buffer