Give your simulator superpowers

RocketSim: An Essential Developer Tool
as recommended by Apple

Win a Let's visionOS 2024 conference ticket. Join for free

Closures in Swift explained with Code Examples

Closures in Swift can be challenging to understand with types like trailing closures, capturing lists, and shorthand syntaxes. They’re used throughout the standard library and are part of the basics you need to know when writing Swift code.

Xcode will help us most of the time with the right syntax using autocompletion but it’s good to know the available options when writing your own.

What are closures?

Closures can be seen as blocks of code that can be passed around in your code. Blocks of code are often referenced as lambdas in other programming languages.

The closure expression syntax looks as follows:

let exampleClosure = { (parameters) -> returntype in
    statements
}

An example from UIKit if for Adding a closure as a target to UIButton and other controls in Swift:

let button = UIButton(type: .system, primaryAction: UIAction(title: "Button Title", handler: { action -> Void in
    print("Button with title \(action.title) tapped!")
}))

The last argument handler is a closure, has a parameter action, and returns Void. The statement prints out the title of the executed action.

We could also write our own closure for multiplying an integer by 2:

let integerMultiplier = { (input: Int) -> Int in
    return input * 2
}
let value = 4
let multipliedValue = integerMultiplier(value)
print(multipliedValue) // Prints: 8

Our integer multiplier code block takes an input integer and returns the input multiplied by 2. As you can see, we’re calling the code block just like we would call a method.

Omitting the result type by inferring from context

A closure can be written in a short-form version by omitting the result type and let it be inferred from the context:

let integerMultiplier = { (input: Int) in
    return input * 2
}

The parameter type can also be inferred to make it even shorter:

let integerMultiplier = { (input) in
    return input * 2
}

We could take this even one step further by omitting the return keyword and parentheses as well:

let integerMultiplier = { input in
    input * 2
}

There’s no common rule for writing closures and it mostly comes down to personal preference. In my opinion, it should be all about readability. If you think your code block is still understandable by taking away extra context it should be fine to write them like that.

Omitting argument names

In some cases, you might not need any of the arguments in code blocks. In those cases, you will see the following error show up in Xcode:

Contextual type for closure argument list expects 1 argument, which cannot be implicitly ignored

You can easily fix this by replacing the argument with an underscore:

let exampleClosure = { _ in
    "I always return this String"
}

Shorthand argument names

Shorthand argument names are always food for thoughts as not everyone likes them. They can make your code less readable as the value names are replaced by dollar-signs followed by an index:

let integerMultiplier = {
    $0 * 2
}

As you can see, we took away most of the body of the closure syntax but our code still works. In this case, your code might still be readable but it’s once again personal preference to replace the describing keyword input with a less describing $0.

The index following the dollar-sign points to the index in the argument list. If our code block has multiple arguments:

let sortedIntegers = [2, 1, 5, 3].sorted { (lhs, rhs) -> Bool in
    return lhs < rhs
}
print(sortedIntegers) // Prints: [1, 2, 3, 5]

We could replace them by dollar-sign references as follows:

let sortedIntegers = [2, 1, 5, 3].sorted {
    $0 < $1
}
print(sortedIntegers) // Prints: [1, 2, 3, 5]

In the above example, our sortedIntegers naming combined with the configured collection of integers gives enough context to understand the sorted closure. However, if you’re unfamiliar with the sorted method you might miss a lot of context by omitting the closure syntax. Therefore, it’s a case per case decision whether or not you like to use this shorthand version within your project and team.

Trailing closures

The last type of code block I want to cover is trailing closures. It’s all in the name: once a code block is used as the last argument we can early close the method call and end with a trailing closure.

Take the following code example of an uploading method:

func upload(_ fileURL: URL, completion: (_ success: Bool) -> Void) {
    /// .. Uploads the file
    /// .. Calls the completion:
    completion(true)
}

upload(fileURL, completion: { success in
    print("File upload success is \(success)!")
})

In this case, we’ve used the closure as a regular parameter. We can replace this with a trailing code block as follows:

upload(fileURL) { success in
    print("File upload success is \(success)!")
}

As you can see, we omitted the completion parameter keyword and we’re now using the closure as a block in the end.

Within a trailing closure we can use all the early named variants like the shorthand argument names:

upload(fileURL) {
    print("File upload success is \($0)!")
}

Capturing values in a closure

Values are captured in closures which basically means that it references values until the block of code is executed. This can lead to retain cycles for which I recommend reading my article Weak self and unowned self explained in Swift to better understand how values are captured.

Escaping closures

An often asked question is about escaping closures: what are they and what is the difference with non-escaping code blocks?

I find the description in Apple’s documentation really valuable:

A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns.

In other words, the code block escapes and might not be called before your method ends. This is often the case when writing asynchronous code.

Xcode uses the escaping statement to know whether values will be retained and can lead to retain cycles. Whenever you write code in which a code block escapes:

func uploadEscaping(_ fileURL: URL, completion: () -> Void) {
    /// .. Uploads the file asynchronous
    DispatchQueue.global().async {
        /// .. Calls the completion:
        completion()
    }
}

Xcode will show an error:

Escaping closure captures non-escaping parameter ‘completion’

The @escaping keyword solves this and explicitly states that the closure can escape:

func uploadEscaping(_ fileURL: URL, completion: @escaping () -> Void) {
    /// .. Uploads the file asynchronous
    DispatchQueue.global().async {
        /// .. Calls the completion:
        completion()
    }
}

Replacing closures with operators or methods

Lastly, operators and methods can be used as input for closures. For example, say that we have a common method for handling completion callbacks for our networking requests:

func handleRequestCompletion(_ result: Result<Void, Error>) {
    print("Request completed: \(result)")
}

We can use that method if the parameters match the input parameters of the code block:

func request(_ url: URL, completion: (_ result: Result<Void, Error>) -> Void) {
    /// Performs the request, calls the completion:
    completion(.success(()))
}
request(requestURL, completion: handleRequestCompletion)

It’s important to realize we’re referencing self.handleRequestCompletion here. In other words, we’re referencing self which could lead to a retain cycle if used with escaping code blocks.

Using operators to replace closures

Operators can be used to replace common closures. A common example is to use an operator for sorting:

let sortedIntegers = [2, 1, 5, 3].sorted(by: <)
print(sortedIntegers) // Prints: [1, 2, 3, 5]

This often leads to more readable code and less code to write.

Conclusion

Closures can be written in many different ways. It’s important to know those possibilities as the code blocks are used in many standard APIs. Shorthand versions are tempting to write but it’s always important to keep readability in mind.

If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!

 

Do you know everything about Swift?

You don't need to master everything, but staying informed is crucial. Join our community of 17,905 developers and stay ahead of the curve:


Featured SwiftLee Jobs

Find your next Swift career step at world-class companies with impressive apps by joining the SwiftLee Talent Collective. I'll match engineers in my collective with exciting app development companies. SwiftLee Jobs