Autoclosure in Swift

autoclosure in Swift example image

Hallo vrienden, Leo hier. Today’s topic is autoclosure in Swift and how you can improve your code using them.

We will explore the @autoclosure annotation. Autoclosure annotation is a great addition to the language and it enables you to define an argument that will be automatically wrapped by a closure. The most important thing is that this enables your API’s users to pass arguments as if they were simple types but behind the scenes, it actually turns automatically into closures.

Autoclosure can leverage your code as they give the opportunity to not execute expensive operations if they aren’t needed. We will check in a few moments about how can you do that.

Let’s code! But first…

 

Painting of The Day

The painting is an 1873 masterpiece called Good Advice is Expensive! by Berthold Woltze. Berthold Woltze (born 24 August 1829 in Havelberg; died 29 November 1896 in Weimar) was a German genre painter, portrait painter, and illustrator. Berthold Woltze was a professor at Weimar Saxon Grand Ducal Art School. In the period from 1871 to 1878, he published numerous of his works in the Gartenlaube newspaper. One of his most famous works is Der lästige Kavalier, translated as “The Irritating Gentleman” or “The Annoying Cavalier.”

I chose this painting because of its title. As we are trying to avoid expensive operations and  @autoclosure it’s important also to know that Good Advice is Expensive.

 

The Problem – Autoclosure in Swift

You are building an API that optionally uses one of the arguments inside it.

Imagine you have a CarProvider struct. It provides a car and receives two arguments. But one of the arguments may not be used during the execution of the method:

struct CarProvider {
    func provideCar(shouldExecute: Bool, expensiveExecution: String) -> String {
        
        // car provider logic here...
        
        if shouldExecute { // Mark 1
            print(expensiveExecution)
        }
        
        return "car"
    }
}

As you can check in Mark 1, the string will only be needed if shouldExecute is true. And for these conditions, you can imagine anything you want: if the user is included in some AB Test, if the user has some flag turned on in user defaults, if the camera is enabled, etc. Think of this like any condition that has to be fulfilled to one of the parameters being used.

If you ever wrote a function that optionally uses the parameter, this article is for you.

So when you test the function, imagine that to get the expensiveExecution String argument, you should do some computations. In our test will just be a removeLast() from an array.

Let’s test the code and see the results:

var carList = ["Ferrori","Aston Markins","Furd"]
let carProvider = CarProvider()

print("Name List before:", carList)

carProvider.provideCar(shouldExecute: false, expensiveExecution: carList.removeLast())

print("Name List after action not executed:", carList)

carProvider.provideCar(shouldExecute: true, expensiveExecution: carList.removeLast())

print("Name List after action executed:", carList)

Can you guess how many items we have in the end?

Check the image below:

The result is: that every time we need expensive information, we actually are calling them. If this would take 2 seconds every time, the user should wait for every invocation. This way we end calling the expensiveExecution two times leaving us with just one item in the array. On top of that, if you consider that this expensiveExecution String may even not be used, we can improve this code using @autoclosure.

 

Using Autoclosure

We can rewrite the code using @autoclosure, like this:

struct CarProvider {
    func provideCar(shouldExecute: Bool, expensiveExecution: @autoclosure () -> String) -> String {
        
        // car provider logic here...
        
        if shouldExecute {
            print(expensiveExecution())
        }
        
        return "car"
    }
}

To test, we’ll use the exact same code:

var carList = ["Ferrori","Aston Markins","Furd"]
let carProvider = CarProvider()

print("Name List before:", carList)

carProvider.provideCar(shouldExecute: false, expensiveExecution: carList.removeLast())

print("Name List after action not executed:", carList)

carProvider.provideCar(shouldExecute: true, expensiveExecution: carList.removeLast())

print("Name List after action executed:", carList)

Although the provideCar car function now receives a closure, the @autoclosure enable us to not explicitly have to call it in the function argument.

Now… Can you guess how many objects will be in the carList when does this code run? Check below:

Autoclosure in Swift example image 3

This is amazing. Even though we calling carList.removeLast() two times, in the end, Swift will only execute when it’s needed. This type of behavior can be misleading when you are coding, this is why you have to be careful when you should use the @autoclosure syntax.

 

Uses of Autoclosure in your everyday code

But you can be asking yourself now, “Cool feature, but I’ll never use this”. Well, probably you are already using it but don’t realize that. A lot of common Swift libraries uses @autoclosure.

For example the testing library :

public func XCTAssertTrue(_ expression: @autoclosure () throws -> Bool, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
    _XCTEvaluateAssertion(.`true`, message: message(), file: file, line: line) {
        let value = try expression()
        if value {
            return .success
        } else {
            return .expectedFailure(nil)
        }
    }
}

And the famous assert function:

func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line)

 

Summary – Autoclosure in Swift

In today post we studied how can we optionally deal with the resolving of a function argument. Remember: if you are building a function that may or may not use one of the arguments, that argument could be an `@autoclosure`.

That’s all my people, I hope you liked reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or leave a comment saying hello. You can also sponsor posts and I’m open to freelance writing! You can reach me on LinkedIn or Twitter and send me an e-mail through the contact page.

Thanks for reading and… That’s all folks.

Credits:

title image

Share this post:

Related posts

Sponsor