Closures
Closures are self-contained blocks of code that do not require the func keyword or a formal name. They can be assigned to variables, passed as arguments to other functions, or returned from other functions. They are similar to "lambdas" or "blocks" in other programming languages.
Closure Syntax (The in keyword)
Closures can take parameters and return values, just like normal functions. Since they lack a name, the entire syntax is written inside the curly braces { }. Swift uses the in keyword to separate the parameters and return type from the actual body of the code.
{ (parameters) -> returnType in
// body of the closure
}Functions vs Closures
While functions and closures are technically the same thing (functions are essentially just named closures), they differ in syntax and everyday usage:
// Named Function
func sayHello(name: String) -> String {
return "Hello, \(name)!"
}
// Closure
let sayHelloClosure = { (name: String) -> String in
return "Hello, \(name)!"
}Trailing Closure Syntax
If a closure is the last argument passed to a function, it can be written outside the function's parentheses for a cleaner look.
func downloadImage(url: String, onCompletion: () -> Void) {
onCompletion()
}
downloadImage(url: "piratedev.com/pic.png", onCompletion: {
print("Action performed!")
})
// Same as above, but uses Trailing Closure Syntax
downloadImage(url: "piratedev.com/pic.png") {
print("Action performed!")
}Shorthand Argument Names
Swift provides automatic names for the arguments inside a closure: $0 for the first, $1 for the second, and so on. This allows you to omit the parameter list and the in keyword entirely for simple one-liners (Inline Closures).
let numbers = [1, 2, 3, 4, 5]
// Full syntax
let doubled = numbers.map { (number: Int) -> Int in
return number * 2
}
// Shorthand syntax
let doubled = numbers.map { $0 * 2 }map is a Higher Order Function and is discussed on the next page.
@escaping Closures
A closure is called escaping when it is passed as an argument to a function but is executed after that function has already returned. This is almost always used in asynchronous tasks like network requests.
Without @escaping, Swift assumes the closure will be finished before the function ends (this is called "non-escaping").
Here is a simple example using a simulated delay to show how it escapes the main function:
import Foundation
func fetchData(completion: @escaping (String) -> Void) {
print("Starting the data fetch...")
// We simulate taking time by delaying the execution for 2 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
// This closure runs long after the fetchData function has already ended
completion("Here is your downloaded data!")
}
// The function returns here, but the closure runs 2 seconds later.
print("The function has finished running. Waiting for the delay...")
}
// Calling the function
fetchData { message in
print(message)
}