How to use Swift @autoclosure

2019-01-16 00:36发布

问题:

I noticed when writing an assert in Swift that the first value is typed as

@autoclosure() -> Bool

with an overloaded method to return a generic T value, to test existence via the LogicValue protocol.

However sticking strictly to the question at hand. It appears to want an @autoclosure that returns a Bool.

Writing an actual closure that takes no parameters and returns a Bool does not work, it wants me to call the closure to make it compile, like so:

assert({() -> Bool in return false}(), "No user has been set", file: __FILE__, line: __LINE__)

However simply passing a Bool works:

assert(false, "No user has been set", file: __FILE__, line: __LINE__)

So what is going on? What is @autoclosure?

Edit: @auto_closure was renamed @autoclosure

回答1:

Consider a function that takes one argument, a simple closure that takes no argument:

func f(pred: () -> Bool) {
    if pred() {
        print("It's true")
    }
}

To call this function, we have to pass in a closure

f(pred: {2 > 1})
// "It's true"

If we omit the braces, we are passing in an expression and that's an error:

f(pred: 2 > 1)
// error: '>' produces 'Bool', not the expected contextual result type '() -> Bool'

@autoclosure creates an automatic closure around the expression. So when the caller writes an expression like 2 > 1, it's automatically wrapped into a closure to become {2 > 1} before it is passed to f. So if we apply this to the function f:

func f(pred: @autoclosure () -> Bool) {
    if pred() {
        print("It's true")
    }
}

f(pred: 2 > 1)
// It's true

So it works with just an expression without the need to wrap it in a closure.



回答2:

Here's a practical example — my print override (this is Swift 3):

func print(_ item: @autoclosure () -> Any, separator: String = " ", terminator: String = "\n") {
    #if DEBUG
    Swift.print(item(), separator:separator, terminator: terminator)
    #endif
}

When you say print(myExpensiveFunction()), my print override overshadows Swift's print and is called. myExpensiveFunction() is thus wrapped in a closure and not evaluated. If we're in Release mode, it will never be evaluated, because item() won't be called. Thus we have a version of print that doesn't evaluate its arguments in Release mode.



回答3:

Description of auto_closure from the docs:

You can apply the auto_closure attribute to a function type that has a parameter type of () and that returns the type of an expression (see Type Attributes). An autoclosure function captures an implicit closure over the specified expression, instead of the expression itself. The following example uses the auto_closure attribute in defining a very simple assert function:

And here's the example apple uses along with it.

func simpleAssert(condition: @auto_closure () -> Bool, message: String) {
    if !condition() {
        println(message)
    }
}
let testNumber = 5
simpleAssert(testNumber % 2 == 0, "testNumber isn't an even number.")

Basically what it means is you pass a boolean expression as that first argument instead of a closure and it automatically creates a closure out of it for you. That's why you can pass false into the method because it is a boolean expression, but can't pass a closure.



回答4:

This shows a useful case of @autoclosure https://airspeedvelocity.net/2014/06/28/extending-the-swift-language-is-cool-but-be-careful/

Now, the conditional expression passed as the first parameter to until will be automatically wrapped up into a closure expression and can be called each time around the loop

func until<L: LogicValue>(pred: @auto_closure ()->L, block: ()->()) {
    while !pred() {
        block()
    }
}

// doSomething until condition becomes true
until(condition) {
    doSomething()
}


回答5:

It's just a way to get rid of the curly braces in a closure call, simple example:

    let nonAutoClosure = { (arg1: () -> Bool) -> Void in }
    let non = nonAutoClosure( { 2 > 1} )

    let autoClosure = { (arg1: @autoclosure () -> Bool) -> Void in }
    var auto = autoClosure( 2 > 1 ) // notice curly braces omitted