Callbacks, as mentioned, allow us to handle asynchronous operations by specifying what should happen when that operation is completed. Simply put, a callback is any piece of executable code that is passed to a function that will call it at some later point, either synchronously or asynchronously. As an example of synchronous callback, in Chapter 3, Diving into Foundation and the Standard Library we used the forEach method available on Array objects:
// Using forEach with a closure:
(1...5).forEach { value in
print("\(value)")
}
This could be equivalently rewritten as follows, where we replace the closure with a function:
// The above is equivalente to this:
func printValue<T>(val : T) { print("\(val)") }
(1...5).forEach(printValue)
These two examples should help clarify that callbacks and closures correspond to different concepts, although with some overlap, since closures can be used to implement callbacks.
Closures, also known as lambdas in functional languages, and as blocks in C and Objective C, are a very powerful mechanism used to define an anonymous function that carries over a portion of the context where it is defined. In technical terms, a closure implements lexically scoped name binding, which means they bind entities belonging to their lexical scope to local identifiers. In less technical terms, a closure can be considered as a function, plus an environment that maps the free variables used in the function to values or references that were bound to the same identifiers in the context where the closure was created. Behaviorally, a closure closes over the variables or constants it refers from the context where it was defined.
Actually, in Swift, global functions and nested functions are special cases of closures. The most poignant form of closure, though, are closure expressions. Those are unnamed functions that can be written using a lightweight syntax that aims to foster a clean, clear, and clutter-free style. Swift strives to make writing closure expressions as easy as it can be, by supporting the following features:
- Parameter and return value type inference based on context information. Let's consider our previous forEach example:
(1...5).forEach { value in
print("\(value)")
}
As you can see, there was no need to specify the type of the closure value parameter, since Swift is able to infer it correctly from the type of the array to which forEach is applied. In general, it is always possible to infer the types of a closure's parameters and return type, but it may be good practice to specify them to improve readability and reduce ambiguity when their purpose is not as straightforward as in the present example. In particular, the Swift compiler will require that you always specify closure complex return types.
- Implicit returns from single-expression closures. A closure expression can get as complex as good style allows, but, in many cases, a closure can be just made of a single expression. In such cases, a further syntax simplification is possible. For example, consider the following closure expression mapped on to an array element:
// No need for return statement in single-expression closures:
let squares1 = (1...5).map { value in
value * value
}
As you can see, the return keyword has been omitted. Yet, the Swift compiler is able to handle the return value in a sensible way.
- Shorthand argument names allow you to refer to closure arguments by their position, for example, by using names such as $0, $1, $2, and so on. This makes it possible to streamline a single-expression closure even further, as seen here:
// Even better:
let squares2 = (1...5).map { $0 * $0 }
Shorthand argument names can be used in any closure expression.
- Trailing closure syntax. If you recall the first two examples we presented in this section, we showed how you could pass a function to forEach in place of a closure. This was possible because forEach has the following declaration, where body is a function that takes an Element and returns Void:
func forEach(_ body: (Element) throws -> Void) rethrows
Now, Swift allows you to replace the last parameter of the function type with a trailing closure after the function call's parentheses. This provides a more natural syntax that makes the closure appear like a block, but it is still just another argument to the function.