How to Use Closures in Swift: A Practical Guide for Beginners, Intermediate and Advanced…
Closures are one of the most powerful features of Swift. They allow you to group code that executes together, without creating a named function. You can use closures to write concise and expressive code that can be passed around and used in different contexts.
Photo by Abdelrahman Sobhy on Unsplash
But what exactly are closures? How do they work? And when should you use them?
In this article, I will explain closures and their practical use cases for beginners, intermediate and advanced programmers. I will also show you some examples of how to write and use closures in Swift.
What are Closures?
According to the official documentation**1**, closures are:
self-contained blocks of functionality that can be passed around and used in your code. Closures can capture and store references to any constants and variables from the context in which they’re defined. This is known as closing over those constants and variables.
In other words, closures are anonymous functions that can access values from their surrounding scope. They have a similar syntax to regular functions, but with some differences:
- You don’t use the
**func**
keyword to create a closure. - You don’t have to give a closure a name (although you can assign it to a variable or constant).
- You can omit the parameter types and return type if they can be inferred from context.
- You can use shorthand argument names (
**$0**
,**$1**
, etc.) instead of explicitly naming them. - You can write a closure as a trailing closure if it is the last argument of a function or method.
Here is an example of how to declare a simple closure that prints “Hello World”:
// Declare a closure
let hello \= {
print("Hello World")
}
// Call the closure
hello()
Notice how we used **{}**
to enclose the closure body, and how we called it using **()**
like a regular function.
Why Use Closures?
Closures have many benefits over regular functions. Some of them are:
- They make your code more concise and readable. You don’t have to write extra lines of code to define a named function that you only use once or twice. You can just write a closure inline where you need it.
- They make your code more flexible. You can pass closures as arguments to other functions or methods, or return them as values from other functions or methods. This allows you to create higher-order functions that operate on other functions (such as map, filter, reduce, etc.)
- They make your code more expressive. You can use closures to create custom operators or DSLs (domain-specific languages) that suit your needs. For example, you can use closures to create animations, network requests, validation rules, etc.
- They make your code more efficient. You can use closures to capture values from their surrounding scope without copying them or creating strong references. This allows you to avoid memory leaks or retain cycles.
How to Use Closures?
There are many ways you can use closures in Swift. Here are some common scenarios where closures come in handy:
As Callbacks
One of the most common uses of closures is as callbacks: blocks of code that are executed after some asynchronous operation completes (such as network requests, timers, animations, etc.)
For example, suppose you want to fetch some data from an API using URLSession:
// Create a URL
let url \= URL(string: "https://example.com/api/data")!
// Create a URLSessionDataTask
let task \= URLSession.shared.dataTask(with: url) { data, response, error in
// This closure is executed when the task finishes
// Check for errors
if let error \= error {
print("Error: \\(error.localizedDescription)")
return
}
// Check for data
guard let data \= data else {
print("No data received")
return
}
// Do something with data (e.g., parse JSON)
}
Notice how we passed a closure as an argument to the [**dataTask(with:completionHandler:)**](https://www.programiz.com/swift-programming/closures)
method2. This closure takes three parameters: **data**
, **response**
, and **error**
. The closure is executed when the task finishes fetching data from the URL.
We could also write this closure as a trailing closure:
// Create a URL
let url \= URL(string: "https://example.com/api/data")!
// Create a URLSessionDataTask using trailing closure syntax
let task \= URLSession.shared.dataTask(with: url) { data, response, error in
// This closure is executed when the task finishes
// Check for errors
if let error \= error {
print("Error: \\(error.localizedDescription)")
return
}
// Check for data
guard let data \= data else {
print("No data received")
return
}
// Do something with data (e.g., parse JSON)
}
// Alternatively, you can write the closure after the parentheses
let task \= URLSession.shared.dataTask(with: url) {
data, response, error in
// Closure body goes here
}
This is called trailing closure syntax1, and it makes your code more concise and readable. You can use this syntax whenever a closure is the last argument of a function or method.
As Higher-Order Functions
Another common use of closures is as higher-order functions: functions that take other functions as arguments or return other functions as values. Swift’s standard library provides many higher-order functions that operate on collections (such as arrays, dictionaries, sets, etc.)
For example, suppose you have an array of numbers:
let numbers \= [1, 2, 3, 4, 5]
You can use the **map**
function to transform each element of the array using a closure:
// Use map to double each element of the array
let doubled \= numbers.map { number in
number * 2
}
// Alternatively, you can use shorthand argument names ($0)
let doubled \= numbers.map { $0 * 2 }
// Print doubled array [2, 4, 6 ,8 ,10]
print(doubled)
The **map**
function takes a closure as an argument and applies it to each element of the array. The result is a new array with the transformed values.
You can also use other higher-order functions such as **filter**
, **reduce**
, **forEach**
, etc. to perform different operations on collections using closures.
As Custom Operators
You can also use closures to create custom operators that perform specific tasks. For example, suppose you want to create an operator that adds two strings with a space between them:
// Define an infix operator named +-
infix operator +-
// Define a function that implements this operator using a closure
func +- (left: String, right: String) -> String {
return left + " " + right
}
// Use this operator to concatenate strings with a space between them
let helloWorld \= "Hello" +- "World" // "Hello World"
let swiftRocks \= "Swift" +- "Rocks" // "Swift Rocks"
Notice how we used a closure to define the function that implements our custom operator. We could also write this function using regular syntax:
func +- (left: String, right: String) -> String {
func addSpace(_ left: String,_ right: String) -> String {
return left + " " + right
}
return addSpace(left,right)
}
But using a closure makes it more concise and elegant.
Conclusion
Closures are powerful tools that allow you to write concise and expressive code in Swift. You can use them for various purposes such as callbacks, higher-order functions, custom operators, animations, validation rules, network requests, and more.
In this article, I have explained the basics of closures and their practical use cases for beginners, intermediate and advanced programmers. I hope you have learned something new and useful from this article.
If you want to learn more about closures and other Swift features, I recommend checking out the following resources:
Thank you for reading and happy coding! 😊
Photo by Nathan Dumlao on Unsplash