Leonardo Maia Pugliese
Holy Swift

Holy Swift

Using Autoclosure to Delay Expensive Function in Swift

Using Autoclosure to Delay Expensive Function in Swift

Improving your code with Autoclosures

Leonardo Maia Pugliese's photo
Leonardo Maia Pugliese
·Nov 26, 2021·

5 min read

Subscribe to my newsletter and never miss my upcoming articles

Hallo vrienden, Leo hier.

Today 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 they were simple types but behind the scenes it actually turns automatically in 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 how can you do that.

Let's code! But first...

The Painting

The painting is a 1873 masterpiece called Good Advice is Expensive! from 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 it's title. As we are trying to avoid expensive operations with @autoclosure it's important also know that Good Advices are Expensive.

The Problem

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

Imagine you have a CarProvider struct. It provides 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 this conditions, you can imagine anything you want: if the user is included 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 have to be fullfiled 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 much items we have in the end?

Check the image below:

Screenshot 2021-11-25 at 16.50.09.png

The result is: every time we need the 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 exactly 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 this code runs? Check below:

Screenshot 2021-11-26 at 07.49.51.png

This is amazing. Even we calling carList.removeLast() two times, in the end, Swift will only execute when it's needed. This type of behaviour 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 every day 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 realise 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

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 as I enjoyed write this article. If you want to support this blog you can Buy Me a Coffee or just leave a comment saying hello. You can also sponsor posts and I'm open to writing freelancing! Just reach me in LinkedIn or Twitter for details.

Thanks for the reading and... That's all folks.

credits: image

Did you find this article valuable?

Support Leonardo Maia Pugliese by becoming a sponsor. Any amount is appreciated!

Learn more about Hashnode Sponsors
 
Share this