Swift Trailing Closures
Introduction
In Swift, closures are self-contained blocks of functionality that can be passed around and used in your code. When a function's last parameter is a closure, Swift provides a special syntax called trailing closure syntax that makes your code cleaner and more readable.
Trailing closures allow you to write the closure outside of the function's parentheses, making complex function calls with closure arguments more elegant and easier to understand, especially when the closure contains multiple lines of code.
Understanding Trailing Closures
Basic Syntax
Here's the standard way to call a function that takes a closure as a parameter:
// Standard closure syntax
function(parameterOne: value) {
// closure body
}
And here's how it looks with trailing closure syntax:
// Trailing closure syntax
function(parameterOne: value) {
// closure body
}
Notice that the syntax looks identical! That's because when the closure is the final parameter, you're allowed to omit the parameter label and move the closure outside the parentheses.
When to Use Trailing Closures
You can use trailing closure syntax when:
- A function's last parameter is a closure
- You're calling the function with that closure parameter
Let's see this in action with a simple example:
// A function that takes two parameters, the second one being a closure
func greet(person: String, message: () -> Void) {
print("Hello, \(person)!")
message()
}
// Calling the function using standard syntax
greet(person: "John", message: {
print("Have a great day!")
})
// Calling the function using trailing closure syntax
greet(person: "John") {
print("Have a great day!")
}
Output for both calls:
Hello, John!
Have a great day!
As you can see, the trailing closure syntax makes the function call cleaner and more readable.
Multiple Trailing Closures
Starting with Swift 5.3, you can use multiple trailing closures in a function call. The first trailing closure still uses the same syntax we've seen, but additional closures use a labeled format:
func processPicture(with filter: (UIImage) -> UIImage, completion: (UIImage) -> Void, onFailure: (Error) -> Void) {
// Function implementation
}
// Using multiple trailing closures
processPicture(with: someFilter) { filteredImage in
// Handle the filtered image
} onFailure: { error in
// Handle any errors
}
Real-World Examples
Example 1: Array Sorting
One common use of trailing closures is with array sorting:
let names = ["Chris", "Alex", "Barry", "Diana"]
// Standard closure syntax
let sortedNames1 = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 < s2
})
// Trailing closure syntax
let sortedNames2 = names.sorted { s1, s2 in
return s1 < s2
}
// Even shorter with implicit return
let sortedNames3 = names.sorted { $0 < $1 }
print(sortedNames3)
Output:
["Alex", "Barry", "Chris", "Diana"]
Example 2: Animations in UIKit
If you're developing iOS apps, you'll often use trailing closures with animations:
// Standard closure syntax
UIView.animate(withDuration: 0.3, animations: {
view.alpha = 0
})
// Trailing closure syntax
UIView.animate(withDuration: 0.3) {
view.alpha = 0
}
Example 3: Network Requests
Trailing closures are commonly used in completion handlers for network requests:
// Function that fetches data from a URL
func fetchData(from url: URL, completion: (Data?, Error?) -> Void) {
// Implementation details...
}
// Using trailing closure syntax
fetchData(from: someURL) { (data, error) in
if let error = error {
print("Error: \(error.localizedDescription)")
return
}
guard let data = data else {
print("No data received")
return
}
// Process the data
print("Downloaded \(data.count) bytes")
}
Common Higher-Order Functions with Trailing Closures
Swift's standard library includes several higher-order functions that work great with trailing closures:
Map
The map
function transforms each element in a collection:
let numbers = [1, 2, 3, 4, 5]
// Standard syntax
let doubled1 = numbers.map({ (number) -> Int in
return number * 2
})
// Trailing closure syntax
let doubled2 = numbers.map { number in
return number * 2
}
// Shortest form with implicit return and shorthand argument
let doubled3 = numbers.map { $0 * 2 }
print(doubled3) // [2, 4, 6, 8, 10]
Filter
The filter
function returns elements that meet a condition:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// Using trailing closure
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers) // [2, 4, 6, 8, 10]
Reduce
The reduce
function combines all items in a collection:
let numbers = [1, 2, 3, 4, 5]
// Using trailing closure
let sum = numbers.reduce(0) { $0 + $1 }
print(sum) // 15
Best Practices for Using Trailing Closures
-
Use trailing closures for readability - Especially for longer closures or when the closure is the primary focus of the function call
-
Consider nesting - Be careful with nested trailing closures as they can reduce readability:
// This can get confusing
fetchData(from: url) { data in
processImage(data) { image in
displayImage(image) {
// More nested code...
}
}
}
- Be consistent - Maintain a consistent style throughout your codebase
Summary
Trailing closures are a syntactic sugar feature in Swift that enhances code readability when working with functions whose last parameter is a closure. This feature is especially valuable when:
- The closure contains multiple lines of code
- The closure is more important than other parameters
- You're working with higher-order functions like
map
,filter
, andreduce
By enabling you to move the closure outside of the function's parentheses, trailing closures make Swift code cleaner, more readable, and more natural to write and understand.
Exercises
-
Convert the following function call to use trailing closure syntax:
swiftlet numbers = [1, 2, 3, 4, 5]
let squaredNumbers = numbers.map({ num in return num * num }) -
Write a function called
processString
that takes a string and a closure that transforms the string. Then call this function using trailing closure syntax. -
Use trailing closure syntax with the
forEach
method to print each element of an array on a new line.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)