Swift Closures

I. Definition

Closures in Swift are defined as self-contained blocks of executable code that can capture variables or constants from their context. This means they can “remember” the environment in which they were created, even after that environment no longer exists. A common way to think of closures is as “nameless functions“though this is a simplification.

II. Forms

In Swift, functions are technically a form of closure, leading to the following insights:

All functions are closures, as they can also capture context if defined within a scope.
However, not all closures are functions, because functions must have names, while closures can be anonymous, written as { ... } expressions.

This distinction is crucial for understanding their flexibility and usage in Swift programming.

Closure are three main forms:

  • Named Functions: Regular functions with names, like func add(x:y:).
  • Nested Functions: Functions defined inside other functions, still named but scoped within.
  • Anonymous Closures: The core focus, written as this.
1
2
3
 { (parameters) -> returnType in // body }, 

// like { (x: Int, y: Int) -> Int in return x + y }.

Ⅲ.Syntax Structure of Closures

The syntax for defining a closure in Swift follows a specific structure, which is essential for practical implementation:

The basic syntax is:

1
2
3
{ (parameters) -> return type in
// body
}

For example, consider the following closure:

1
2
3
let addClosure: (Int, Int) -> Int = { (a, b) in
return a + b
}

This can be compared to a named function for clarity:

1
2
3
func addFunction(a: Int, b: Int) -> Int {
return a + b
}

The syntax highlights the parameters, return type, and body, separated by the in keyword, which delineates the declaration from the implementation.
This structure is intuitive once familiar, and examples like the above illustrate its equivalence to named functions, reinforcing the concept that all functions are closures.

Ⅳ.Practical Uses: Closures as First-Class Citizens in Action

If you don’t konw what is “First-Class Citizens”, I recommand you visit this article Functions as First-Class Citizens

The first-class citizen status of closures translates into three key uses, each demonstrated with examples:

1.Assigning Closures to Constants or Variables

You can define a closure and assign it to a variable or constant, then call it as needed. For instance:

1
2
3
4
let add = { (a: Int, b: Int) -> Int in
return a + b
}
print(add(2, 3)) // Outputs 5

This shows how closures can be stored and executed like functions, enhancing code reusability.

2.Passing Closures as Parameters to Functions

Closures can be passed as arguments to functions, enabling dynamic behavior. Consider:

1
2
3
4
5
func performOperation(_ operation: (Int, Int) -> Int) {
print("Result:", operation(3, 2))
}

performOperation(add) // Outputs 5

You can also pass the closure directly as an expression:

1
2
3
4
performOperation { (a, b) in
return a * b
}

For brevity, Swift allows simplification using shorthand argument names:

1
performOperation { $0 * $1 }

Additionally, Swift supports trailing closure(I will talk about this in another article) syntax, which is particularly useful when the last parameter is a closure, making the code more readable:

1
performOperation { print("Result of multiplication:", $0 * $1) }

This syntactic sugar is a hallmark of Swift’s design, promoting concise and elegant code.

3.Closures as Return Values of Functions

Functions can return closures, creating reusable code blocks. For example:

1
2
3
4
5
6
func getAdditionClosure() -> (Int, Int) -> Int {
return { $0 + $1 }
}

let addFunc = getAdditionClosure()
print(addFunc(4, 5)) // Outputs 9

This use case is powerful for creating factory-like functions that generate closures with specific behaviors, such as incrementers or calculators.

The Essential Value: Capturing Context

The true power of closures lies in their ability to capture and “enclose“ variables or constants from their context, retaining these values even after the original scope has ended.

Consider this example to illustrate:

1
2
3
4
5
6
7
8
9
10
11
func makeIncrementer(by amount: Int) -> () -> Int {
var total = 0
return {
total += amount
return total
}
}

let incrementByTwo = makeIncrementer(by: 2)
print(incrementByTwo()) // Outputs 2
print(incrementByTwo()) // Outputs 4

Here, the closure captures total and amount, remembering the state even after makeIncrementer has returned. This demonstrates the closure’s ability to maintain and update context, a feature that is essential for managing state in asynchronous programming, such as handling UI updates or network responses.

This is particularly valuable in scenarios like asynchronous operations, UI callbacks, and network requests, where maintaining state is crucial.

Ⅴ.Summary

To consolidate the learning, here are the key points summarized in a table for easy reference:

Keyword Explanation
Closures are variants of functions All functions are actually closures, as they can capture context.
Closures can be nameless Defined as { … } expressions, offering flexibility for inline use.
Closures are first-class citizens Can be assigned to variables, passed as parameters, and returned from functions, enhancing their utility.
Closures capture context Retain external variable values, crucial for asynchronous logic like UI callbacks and network requests.
Trailing closure A Swift syntactic sugar that makes code concise and elegant, especially for the last parameter being a closure.

In the next article, we will discuss the Trailing closure.

Ⅵ.References