Come usare Swift @autoclosure


148

Ho notato quando scrivevo assertin Swift che il primo valore è digitato come

@autoclosure() -> Bool

con un metodo sovraccarico per restituire un Tvalore generico , per testare l'esistenza tramite LogicValue protocol.

Tuttavia, attenendosi rigorosamente alla domanda in questione. Sembra voler un @autoclosureche ritorna a Bool.

Scrivere una chiusura effettiva che non accetta parametri e restituisce un Bool non funziona, vuole che io chiami la chiusura per farlo compilare, in questo modo:

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

Tuttavia, semplicemente passando un Bool funziona:

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

Quindi che sta succedendo? Che cosa è @autoclosure?

Modifica: è @auto_closure stato rinominato@autoclosure

Risposte:


269

Considera una funzione che accetta un argomento, una semplice chiusura che non accetta alcun argomento:

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

Per chiamare questa funzione, dobbiamo passare in una chiusura

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

Se omettiamo le parentesi graffe, stiamo trasmettendo un'espressione e questo è un errore:

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

@autoclosurecrea una chiusura automatica attorno all'espressione. Quindi, quando il chiamante scrive un'espressione simile 2 > 1, viene automaticamente racchiuso in una chiusura per diventare {2 > 1}prima che venga passato f. Quindi se lo applichiamo alla funzione f:

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

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

Quindi funziona solo con un'espressione senza la necessità di avvolgerla in una chiusura.


2
In realtà l'ultimo, non funziona. Dovrebbe esseref({2 >1}())
Rui Peres,

@JoelFischer Sto vedendo la stessa cosa di @JackyBoy. La chiamata f(2 > 1)funziona. La chiamata f({2 > 1})non riesce con error: function produces expected type 'Bool'; did you mean to call it with '()'?. L'ho provato in un parco giochi e con lo Swift REPL.
Ole Begemann,

In qualche modo ho letto la penultima risposta come ultima risposta, dovrò ricontrollare, ma avrebbe senso se fallisse, dato che in pratica stai mettendo una chiusura dentro una chiusura, da quello che capisco.
Joel Fischer,

3
c'è un post sul blog sul motivo per cui l'hanno fatto developer.apple.com/swift/blog/?id=4
mohamed-ted

5
Ottima spiegazione Nota anche che in Swift 1.2 'autoclosure' è ora un attributo della dichiarazione dei parametri, quindi èfunc f(@autoclosure pred: () -> Bool)
Masa,

30

Ecco un esempio pratico: il mio printoverride (questo è Swift 3):

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

Quando dici print(myExpensiveFunction()), il mio printoverride mette in ombra quello di Swift printe viene chiamato. myExpensiveFunction()viene quindi avvolto in una chiusura e non valutato . Se siamo in modalità Rilascio, non verrà mai valutato, perché item()non verrà chiamato. Quindi abbiamo una versione printche non valuta i suoi argomenti in modalità Release.


Sono in ritardo alla festa, ma qual è l'impatto della valutazione myExpensiveFunction()? Se invece di utilizzare la chiusura automatica passi la funzione per stampare come print(myExpensiveFunction), quale sarebbe l'impatto? Grazie.
crom87

11

Descrizione di auto_closure dai documenti:

È possibile applicare l'attributo auto_closure a un tipo di funzione che ha un tipo di parametro di () e che restituisce il tipo di un'espressione (vedere Attributi del tipo). Una funzione di chiusura automatica acquisisce una chiusura implicita sull'espressione specificata, anziché sull'espressione stessa. L'esempio seguente utilizza l'attributo auto_closure per definire una funzione di asserzione molto semplice:

Ed ecco l'esempio che Apple usa insieme ad esso.

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

Fondamentalmente, ciò significa che passi un'espressione booleana come primo argomento anziché una chiusura e crea automaticamente una chiusura per te. Ecco perché puoi passare false nel metodo perché è un'espressione booleana, ma non può passare una chiusura.


15
Si noti che non è necessario utilizzare @auto_closurequi. Il codice funziona bene senza di essa: func simpleAssert(condition: Bool, message: String) { if !condition { println(message) } }. Utilizzare @auto_closurequando è necessario valutare ripetutamente un argomento (ad esempio, se si implementa una whilefunzione simile) o è necessario ritardare la valutazione di un argomento (ad esempio, se si implementa un cortocircuito &&).
Nathan,

1
@nathan Ciao, Nathan. Potresti per favore citarmi un esempio sull'uso di autoclosurecon una whilefunzione simile? Non mi sembra di capirlo. Grazie mille in anticipo.
Unheilig,

@connor Potresti voler aggiornare la tua risposta per Swift 3.
Jarora,

4

Questo mostra un utile caso di @autoclosure https://airspeedvelocity.net/2014/06/28/extending-the-swift-language-is-cool-but-be-careful/

Ora, l'espressione condizionale passata come primo parametro a fino a quando verrà automaticamente racchiusa in un'espressione di chiusura e può essere chiamata ogni volta attorno al ciclo

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

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

2

È solo un modo per sbarazzarsi delle parentesi graffe in una chiamata di chiusura, un semplice esempio:

    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

0

@autoclosureè un parametro di funzione che accetta una funzione cucinata (o un tipo restituito) mentre un generale closureaccetta una funzione grezza

  • Il parametro del tipo di argomento @autoclosure deve essere '()'
    @autoclosure ()
  • @autoclosure accetta qualsiasi funzione con solo il tipo restituito appropriato
  • Il risultato della chiusura è calcolato dalla domanda

Diamo un'occhiata ad esempio

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()
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.