What is @autoclosure in Swift?
It's on very rare or never occasion that I have ever used a @autoclosure
keyword while passing a closure to method - A closure which takes no parameter and returns a value (No value type) back.
In this post I will try to decipher the hazy definition of @autoclosure
that we all have.
@autoclosure
allows you to avoid normal closure related braces
One of the advantages of annotating passed closure as @autoclosure
is syntactical convenience. It allows you do avoid braces around function's parameter by writing normal expression instead of explicit closure.
For example, look at the following code with no @autoclosure
func complexFunction(clo: () -> String) {
print("Inside Function")
}
With no autoclosure
annotation, the call to function complexFunction
will look like this,
complexFunction { () -> String in
return "Nothing"
}
// OR
complexFunction(clo: { "Nothing" })
However, we can make things simpler by adding autoclosure
annotation to closure clo
func complexFunction(clo: @autoclosure () -> String) {
print("Inside Function")
}
// Now simply call the function without closure like braces.
complexFunction(clo: "Nothing")
As Apple documents, it is common to call methods which take @autoclosure
as parameter, but it's rare to implement such functions.
- Autoclosure lets you delay the closure execution
This is because code is not executed until closure annotated with @autoclosure
is called. Look at the following example.
func performComplexComputation() -> String {
return "Complex"
}
func complexFunction(clo: @autoclosure () -> String, condition: Bool = false) {
print("Inside Function")
if (condition) {
_ = clo()
}
}
complexFunction(clo: performComplexComputation())
In the example above, closure is marked with @autoclosure
annotation and method performComplexComputation
won't be called until the closure is executed from inside of function complexFunction
which takes closure clo
as an input parameter which is annotated with @autoclosure
.
The advantage here is, since performComplexComputation
is computationally expensive, we perform conditional execution. This function won't be executed unless condition
parameter thus passed is true.
Pertaining to this second point, the quote straight from the Apple
The
assert(condition:message:file:line:)
function takes anautoclosure
for its condition and message parameters; its condition parameter is evaluated only in debug builds and its message parameter is evaluated only if condition is false.
- Escaping
autoclosure
When closure is annotated with @autoclosure
, is automatically becomes non-escaping
. If you want to store passed closure to class property or pass it to another function which takes @autoclosure
closure as an input, you will have to explicitly make it as escaping
with @autoclosure(escaping)
var temp: (() -> String)?
func complexFunction(clo: @autoclosure @escaping () -> String) {
print("Inside Function")
// Closure is escaping, so you can assign to an instance variable.
self.temp = clo
}
func performComplexComputation() -> String {
return "Complex"
}
// Please note how we have to use self to call
// performComplexComputation when closure is marked
// with @escaping attribute
complexFunction(clo: self.performComplexComputation())
Few things to note,
-
Swift does not allow non-escaping closures to pass as a parameter to a function which takes escaping closure. However, escaping closure are allowed to pass as a parameter to a function with non-escaping closure
-
Every time you want to store passed closure, store it in instance variable or pass it down to next method make sure to use
@escaping
attribute (In addition to@autoclosure
if that is applicable.) -
Closures are references, meaning if you assign a closure to two different constants or variables, both of those constants or variables will refer to the same closure
func complexFunction(clo: () -> String) {
print("Inside Function")
// temporary1 and temporary2 refer to the same closure reference.
let temporary1 = clo
let temporary2 = clo
}
Last but not least, Apple warns against using too many @autoclosure
in the code. This is because it is not always apparent if closure that gets passed uses @autoclosure
or not making code harder to understand and reason about. Unless there is strong use case of using one like assert
example above or enough documentation is provided explaining the use and reason to use the autoclosure
in relevant places. To quote,
Overusing autoclosures can make your code hard to understand. The context and function name should make it clear that evaluation is being deferred.
References and further reading:
Apple Developers