Nonescaping Closures
By default, when a closure is passed into a function it is considered nonescaping. A nonescaping closure can only be used within the body of the function and nowhere else. To simplify, a nonescaping closure is trapped inside the body of a function.
func performThisClosure(closure: (Void) -> Void) {
closure() // by default, this closure is nonescaping, it's trapped inside this function.
}
The function below printItNow
takes a closure parameter called it
. The it
closure is only used within the body of the function.
func printItNow(it: (Void) -> Void) {
it() // the `it` closure never escapes the body of the function
}
If we call the function printItNow
, then the closure executes immediately and outputs "print me now!".
printItNow {
print("print me now!")
}
Escaping Closures
There are cases like asynchronous control flow when a closure should be able to escape the body of the function to which it is passed. When this happens, we must specify a closure parameter as escaping.
var somethingToDo: (Void) -> Void = {}
func doItLater(it: @escaping (Void) -> Void) {
// keep a reference to `it` so we can call it later...
somethingToDo = it
}
The function doItLater takes a closure parameter also called it. In the body of doItLater, the it closure is saved so that it can be used at a later time. This forces us to specify it as escaping (by using the @escaping syntax) because it can be called after doItLater finishes executing.
var somethingToDo: (Void) -> Void = {}
func doItLater(it: @escaping (Void) -> Void) {
// keep a reference to `it` so we can call it later...
somethingToDo = it
}
// calling `doItLater` only keeps a reference to the closure
doItLater {
print("print me later...")
}
// here is where we call the closure (after doItLater has finished) and "print me later..." is output
somethingToDo()
Why Escaping Closures?
When a closure parameter needs to escape the body of a function, it must explicitly be declared as escaping by using the @escaping syntax. This special marking for closure parameters is primarily a mechanism for improving performance and optimizing memory allocation for Swift.
Using Escaping Closures for Asynchronous Tasks
Say we want to write a function that downloads an image over the network. When the function completes, we want to update the interface to display the newly downloaded image.
It might look something like this.
func downloadAndDisplayImage(imageURL: URL, updateImage: @escaping (UIImage?) -> Void) {
// create network request
let task = URLSession.shared.dataTask(with: myImageURL) { (data, response, error) in
// if no error, then create image and pass it to the completion handler
if let downloadedImage = UIImage(data: data!), error == nil {
DispatchQueue.main.async {
updateImage(downloadedImage)
}
} else {
// otherwise, pass nil to the completion handler
DispatchQueue.main.async {
updateImage(nil)
}
}
}
task.resume()
}
The updateImage
parameter is a closure used to update a hypothetical interface with the downloaded image. It is specified as an escaping closure. This is because the updateImage
closure needs to outlive the downloadAndDisplayImage
function.
downloadAndDisplayImage(imageURL: myImageURL) { downloadedImage in
// after network request finishes, update interface with downloadImage
}
When downloadAndDisplayImage
is called it kicks off a network request and the function quickly exits. However, the closure we pass as a parameter still needs to be called once the network request finishes which happens at an indeterminate amount of time after downloadAndDisplayImage
exits. Therefore, the closure will be used outside of the body of the original function (after the network request finishes) and should be declared as escaping.