What is @autoclosure in Swift?

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 an autoclosure 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,

  1. 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

  2. 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.)

  3. 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

krakendev.io