Como usar o Swift @autoclosure

148

Notei ao escrever um assertem Swift que o primeiro valor é digitado como

@autoclosure() -> Bool

com um método sobrecarregado para retornar um Tvalor genérico , para testar a existência via LogicValue protocol.

No entanto, mantendo estritamente a questão em questão. Parece querer um @autoclosureque retorne a Bool.

Escrever um fechamento real que não aceita parâmetros e retorna um Bool não funciona, ele quer que eu chame o fechamento para compilá-lo, assim:

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

No entanto, simplesmente passar um Bool funciona:

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

Então, o que está acontecendo? O que é @autoclosure?

Editar: @auto_closure foi renomeado@autoclosure

Joel Fischer
fonte

Respostas:

269

Considere uma função que aceita um argumento, um fechamento simples que não aceita argumentos:

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

Para chamar essa função, temos que passar um fechamento

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

Se omitirmos o aparelho, estamos passando uma expressão e isso é um erro:

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

@autoclosurecria um fechamento automático em torno da expressão. Portanto, quando o chamador escreve uma expressão como 2 > 1, ela é automaticamente envolvida em um fechamento para se tornar {2 > 1}antes de ser passada para f. Portanto, se aplicarmos isso à função f:

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

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

Portanto, ele funciona apenas com uma expressão sem a necessidade de envolvê-la em um fechamento.

eddie_c
fonte
2
Na verdade, o último, não funciona. Deveria serf({2 >1}())
Rui Peres
@JoelFischer Estou vendo a mesma coisa que @JackyBoy. Ligar f(2 > 1)funciona. A chamada f({2 > 1})falha com error: function produces expected type 'Bool'; did you mean to call it with '()'?. Eu testei em um playground e com o Swift REPL.
precisa
De alguma forma, li a segunda para a última resposta como a última resposta, vou ter que verificar novamente, mas faria sentido se falhasse, pois você está basicamente colocando um fechamento dentro de um fechamento, pelo que entendi.
Joel Fischer
3
há um post sobre o motivo que eles fizeram isso developer.apple.com/swift/blog/?id=4
Mohamed-ted
5
Ótima explicação. Observe também que no Swift 1.2 'autoclosure' agora é um atributo da declaração de parâmetro, por isso éfunc f(@autoclosure pred: () -> Bool)
Masa
30

Aqui está um exemplo prático - minha printsubstituição (aqui é Swift 3):

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

Quando você diz print(myExpensiveFunction()), minha printsubstituição obscurece a de Swift printe é chamada. myExpensiveFunction()é, portanto, envolto em um fechamento e não avaliado . Se estivermos no modo Release, ele nunca será avaliado, porque item()não será chamado. Portanto, temos uma versão printque não avalia seus argumentos no modo Release.

mate
fonte
Estou atrasado para a festa, mas qual é o impacto da avaliação myExpensiveFunction()? Se, em vez de usar o fechamento automático, você passar a função para imprimir como print(myExpensiveFunction), qual seria o impacto? Obrigado.
Crom87 22/06/19
11

Descrição do auto_closure dos documentos:

Você pode aplicar o atributo auto_closure a um tipo de função que possui um tipo de parâmetro de () e que retorna o tipo de uma expressão (consulte Atributos de tipo). Uma função de autoclosure captura um fechamento implícito sobre a expressão especificada, em vez da própria expressão. O exemplo a seguir usa o atributo auto_closure na definição de uma função assert muito simples:

E aqui está o exemplo que a Apple usa junto.

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

Basicamente, o que isso significa é que você passa uma expressão booleana como o primeiro argumento em vez de um fechamento e ele cria automaticamente um fechamento para você. É por isso que você pode passar falso para o método porque é uma expressão booleana, mas não pode passar um fechamento.

Connor
fonte
15
Observe que você realmente não precisa usar @auto_closure aqui. O código funciona bem sem ele: func simpleAssert(condition: Bool, message: String) { if !condition { println(message) } }. Use @auto_closurequando precisar avaliar um argumento repetidamente (por exemplo, se você estiver implementando uma whilefunção semelhante) ou se precisar atrasar a avaliação de um argumento (por exemplo, se você estiver implementando um curto-circuito &&).
Nathan
1
@nathan Oi, Nathan. Você poderia me citar uma amostra referente ao uso deautoclosure uma whilefunção semelhante a? Parece que não entendi isso. Muito obrigado antecipadamente.
Unheilig
@connor Você pode querer atualizar sua resposta para Swift 3.
jarora
4

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

Agora, a expressão condicional passada como o primeiro parâmetro para até será automaticamente agrupada em uma expressão de fechamento e pode ser chamada sempre que ocorrer o loop

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

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

É apenas uma maneira de se livrar das chaves em uma chamada de encerramento, exemplo simples:

    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
Bobby
fonte
0

@autoclosureé um parâmetro de função que aceita uma função cozida (ou tipo retornado) enquanto um general closureaceita uma função bruta

  • O parâmetro do tipo de argumento @autoclosure deve ser '()'
    @autoclosure ()
  • @autoclosure aceita qualquer função apenas com o tipo retornado apropriado
  • O resultado do fechamento é calculado pela demanda

Vamos dar uma olhada no exemplo

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
fonte