Cómo usar Swift @autoclosure

148

Al escribir un assertmensaje en Swift, noté que el primer valor se escribe como

@autoclosure() -> Bool

con un método sobrecargado para devolver un Tvalor genérico , para probar la existencia a través de LogicValue protocol.

Sin embargo, apegarse estrictamente a la pregunta en cuestión. Parece querer un @autoclosureque devuelve unBool .

Escribir un cierre real que no toma parámetros y devuelve un Bool no funciona, quiere que llame al cierre para que se compile, así:

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

Sin embargo, simplemente pasar un Bool funciona:

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

¿Entonces qué está pasando? Que es@autoclosure ?

Editar: @auto_closure fue renombrado@autoclosure

Joel Fischer
fuente

Respuestas:

269

Considere una función que toma un argumento, un cierre simple que no toma argumento:

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

Para llamar a esta función, tenemos que pasar un cierre

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

Si omitimos las llaves, estamos pasando una expresión y eso es un error:

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

@autoclosurecrea un cierre automático alrededor de la expresión. Entonces, cuando la persona que llama escribe una expresión como 2 > 1, se envuelve automáticamente en un cierre {2 > 1}antes de pasarla f. Entonces, si aplicamos esto a la función f:

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

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

Por lo tanto, funciona con solo una expresión sin la necesidad de envolverlo en un cierre.

eddie_c
fuente
2
En realidad el último, no funciona. Debería serf({2 >1}())
Rui Peres
@JoelFischer Estoy viendo lo mismo que @JackyBoy. Llamar f(2 > 1)funciona. La llamada f({2 > 1})falla con error: function produces expected type 'Bool'; did you mean to call it with '()'?. Lo probé en un patio de recreo y con Swift REPL.
Ole Begemann
De alguna manera leí la penúltima respuesta como la última respuesta, tendré que verificarlo dos veces, pero tendría sentido si fallara, ya que básicamente está poniendo un cierre dentro de un cierre, por lo que entiendo.
Joel Fischer
3
hay una publicación de blog sobre la razón por la que lo hicieron developer.apple.com/swift/blog/?id=4
mohamed-ted
55
Gran explicación Tenga en cuenta también que en Swift 1.2 'autoclosure' ahora es un atributo de la declaración del parámetro, por lo que esfunc f(@autoclosure pred: () -> Bool)
Masa
30

Aquí hay un ejemplo práctico: mi printanulación (esto es Swift 3):

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

Cuando dices print(myExpensiveFunction()), mi printanulación eclipsa a Swift printy se llama. myExpensiveFunction()se envuelve así en un cierre y no se evalúa . Si estamos en modo Release, nunca se evaluará, porque item()no se llamará. Por lo tanto, tenemos una versión printque no evalúa sus argumentos en el modo Release.

mate
fuente
Llego tarde a la fiesta, pero ¿cuál es el impacto de la evaluación myExpensiveFunction()? Si en lugar de usar el cierre automático, pasa la función para imprimir print(myExpensiveFunction), ¿cuál sería el impacto? Gracias.
crom87
11

Descripción de auto_closure de los documentos:

Puede aplicar el atributo auto_closure a un tipo de función que tenga un tipo de parámetro de () y que devuelva el tipo de una expresión (consulte Atributos de tipo). Una función de cierre automático captura un cierre implícito sobre la expresión especificada, en lugar de la expresión misma. El siguiente ejemplo utiliza el atributo auto_closure para definir una función de aserción muy simple:

Y aquí está el ejemplo que Apple usa junto con él.

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

Básicamente, lo que significa es que pasa una expresión booleana como primer argumento en lugar de un cierre y automáticamente crea un cierre para usted. Es por eso que puede pasar falso al método porque es una expresión booleana, pero no puede pasar un cierre.

Connor
fuente
15
Tenga en cuenta que en realidad no necesita usar @auto_closureaquí. El código funciona bien sin él: func simpleAssert(condition: Bool, message: String) { if !condition { println(message) } }. Úselo @auto_closurecuando necesite evaluar un argumento repetidamente (por ejemplo, si estaba implementando una whilefunción similar) o si necesita retrasar la evaluación de un argumento (por ejemplo, si estaba implementando un cortocircuito &&).
nathan
1
@nathan Hola, nathan. ¿Podría por favor citarme una muestra sobre el uso de autoclosureuna whilefunción similar? No parece que me dé cuenta de eso. Muchas gracias de antemano.
Unheilig
@connor Es posible que desee actualizar su respuesta para Swift 3.
jarora
4

Esto muestra un caso útil de @autoclosure https://airspeedvelocity.net/2014/06/28/extending-the-swift-language-is-cool-but-be-careful/

Ahora, la expresión condicional que se pasa como primer parámetro a hasta se envolverá automáticamente en una expresión de cierre y se puede invocar cada vez que se repita el ciclo.

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

// doSomething until condition becomes true
until(condition) {
    doSomething()
}
onmyway133
fuente
2

Es solo una forma de deshacerse de las llaves en una llamada de cierre, ejemplo simple:

    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
Poli
fuente
0

@autoclosurees un parámetro de función que acepta una función cocida (o tipo devuelto) mientras que un general closureacepta una función sin procesar

  • El parámetro de tipo de argumento @autoclosure debe ser '()'
    @autoclosure ()
  • @autoclosure acepta cualquier función con solo el tipo devuelto apropiado
  • El resultado del cierre se calcula por demanda

Veamos un ejemplo.

func testClosures() {

    //closures
    XCTAssertEqual("fooWithClosure0 foo0", fooWithClosure0(p: foo0))
    XCTAssertEqual("fooWithClosure1 foo1 1", fooWithClosure1(p: foo1))
    XCTAssertEqual("fooWithClosure2 foo2 3", fooWithClosure2(p: foo2))

    XCTAssertEqual("fooWithClosure2 foo2 3", fooWithClosure2(p: { (i1, i2) -> String in
        return "fooWithClosure2 " + "foo2 " + String(i1 + i2)
    }))

    //@autoclosure
    XCTAssertEqual("fooWithAutoClosure HelloWorld", fooWithAutoClosure(a: "HelloWorld"))

    XCTAssertEqual("fooWithAutoClosure foo0", fooWithAutoClosure(a: foo0()))
    XCTAssertEqual("fooWithAutoClosure foo1 1", fooWithAutoClosure(a: foo1(i1: 1)))
    XCTAssertEqual("fooWithAutoClosure foo2 3", fooWithAutoClosure(a: foo2(i1: 1, i2: 2)))

}

//functions block
func foo0() -> String {
    return "foo0"
}

func foo1(i1: Int) -> String {
    return "foo1 " + String(i1)
}

func foo2(i1: Int, i2: Int) -> String {
    return "foo2 " + String(i1 + i2)
}

//closures block
func fooWithClosure0(p: () -> String) -> String {
    return "fooWithClosure0 " + p()
}

func fooWithClosure1(p: (Int) -> String) -> String {
    return "fooWithClosure1 " + p(1)
}

func fooWithClosure2(p: (Int, Int) -> String) -> String {
    return "fooWithClosure2 " + p(1, 2)
}

//@autoclosure
func fooWithAutoClosure(a: @autoclosure () -> String) -> String {
    return "fooWithAutoClosure " + a()
}
yoAlex5
fuente