Closures are a powerful programming concept that enable many different programming patterns. However, for lots of beginning programmers, closures can be tricky to use and understand. This is especially true when closures are used in an asynchronous context. For example, when they’re used as completion handlers or if they’re passed around in an app so they can be called later.
In this post, I will explain what closures are in Swift, how they work, and most importantly I will show you various examples of closures with increasing complexity.
What are Closures?
Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages.
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. Swift handles all of the memory management of capturing for you.
Global and nested functions, are actually special cases of closures. Closures take one of three forms:
- Global functions are closures that have a name and don’t capture any values.
- Nested functions are closures that have a name and can capture values from their enclosing function.
- Closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context.
Closures are not unique concept to Swift. For example, languages like JavaScript and Python both have support for closures. Even Objective-C or Smalltalk-80 had kind of closures but called them blocks. In some ways, you can think of a closure as an instance of a function that has access to a specific context and/or captures specific values and can be called later.
var count = 0
let incrementCountBy = { (delta: Int) -> Void in
count += delta
print(count)
}
incrementCountBy(3) // prints 3
incrementCountBy(2) // prints 5
Objective-C Blocks provide a mechanism for passing around code to be run along with captured environment state from when and where the block was defined. The syntax for blocks is concisely described at fuckingblocksyntax.com. While not all that different from similar constructs in other languages, the interaction of the capture process and reference-counting memory management requires some special care.
Any variables that are available in the local scope in which a block is defined and are used by the code in the block are “captured” by that block. For basic types, the default capture is a constant copy; for object types, the default capture is a strong reference (the actual pointer is a constant copy). Variables declared with the
https://bytes.vokal.io/__block
storage qualifier are, in effect, captured by reference—that is, if the value stored in a__block
variable changes after the block’s declaration, subsequent executions of the block will see the new value.
Closure Syntax
Swift’s closure expressions have a clean, clear style, with optimizations that encourage brief, clutter-free syntax in common scenarios. These optimizations include:
- Inferring parameter and return value types from context
- Implicit returns from single-expression closures
- Shorthand argument names
- Trailing closure syntax
func ascending(n1: Int, n2: Int) -> Bool {
return n1 < n2
}
// Sort method takes function or closure
var sortedNumbers = numbers.sorted(by: ascending)
// Using Closure expression in trailing closure
sortedNumbers = numbers.sorted { (n1: Int, n2: Int) -> Bool in
return n1 < n2
}
// Type of parameters and returned type can be always inferred
sortedNumbers = numbers.sorted { n1, n2 in
return n1 < n2
}
// Return keyword not needed in single line closure
sortedNumbers = numbers.sorted { n1, n2 in
n1 < n2
}
// Use implicit paramters names
sortedNumbers = numbers.sorted { $0 < $1 }
Understanding capture lists in closures
A capture list in Swift specifies values to capture from its environment. Whenever you want to use a value that is not defined in the same scope as the scope that your closure is created in, or if you want to use a value that is owned by a class, you need to be explicit about it by writing a capture list.
class CounterClass {
var counter = 1
lazy var closure: () -> Void = {
print(counter)
}
}
This code will not compile due to the following error:
Reference to property `counter` requires explicit use of `self` to make capture semantics explicit.
In other words, we’re trying to capture a property that belongs to a class and we need to be explicit in how we capture this property.
class CounterClass {
var counter = 1
lazy var closure: () -> Void = { [self] in
print(counter)
}
}
A capture list is written using brackets and contains all the values that you want to capture. Capture lists are written before argument lists.
This example has an issue because it strongly captures self
. This means that self
has a reference to the closure, and the closure has a strong reference to self
. We can fix this in two ways:
- We capture
self
weakly - We capture
counter
directly
In this case, the first approach is probably what we want:
class CounterClass {
var counter = 1
lazy var closure: () -> Void = { [weak self] in
guard let self = self else {
return
}
print(self.counter)
}
}
let instance = CounterClass()
instance.closure() // prints 1
instance.counter += 1
instance.closure() // prints 2
Note that inside of the closure I use Swift’s regular guard let
syntax to unwrap self
. If we would not do this we would have to reference self
as self?
– as self becomes an opional with the weak declaration and can be nil.
If I go for the second approach and capture counter
, the code would look as follows:
class CounterClass {
var counter = 1
lazy var closure: () -> Void = { [counter] in
print(counter)
}
}
let instance = CounterClass()
instance.closure() // prints 1
instance.counter += 1
instance.closure() // prints 1
The closure itself looks a litte cleaner now, but the value of counter is captured. This is actually not very common.
For that reason, the example that used weak self
to avoid a retain cycle did read the latest value of counter
.