Verwendung von Swift @autoclosure

148

Beim Schreiben eines assertin Swift ist mir aufgefallen, dass der erste Wert als eingegeben wird

@autoclosure() -> Bool

mit einer überladenen Methode, um einen generischen TWert zurückzugeben, um die Existenz über die zu testen LogicValue protocol.

Halten Sie sich jedoch strikt an die vorliegende Frage. Es scheint ein zu wollen @autoclosure, das a zurückgibt Bool.

Das Schreiben eines tatsächlichen Abschlusses, der keine Parameter akzeptiert und einen Bool zurückgibt, funktioniert nicht. Ich möchte den Abschluss aufrufen, damit er kompiliert wird, wie folgt:

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

Das einfache Übergeben eines Bool funktioniert jedoch:

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

Also, was ist los? Was ist @autoclosure?

Bearbeiten: @auto_closure wurde umbenannt@autoclosure

Joel Fischer
quelle

Antworten:

269

Stellen Sie sich eine Funktion vor, die ein Argument akzeptiert, einen einfachen Abschluss, der kein Argument akzeptiert:

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

Um diese Funktion aufzurufen, müssen wir einen Abschluss übergeben

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

Wenn wir die geschweiften Klammern weglassen, übergeben wir einen Ausdruck und das ist ein Fehler:

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

@autoclosureErstellt einen automatischen Abschluss um den Ausdruck. Wenn der Aufrufer einen Ausdruck wie schreibt 2 > 1, wird er automatisch in einen Abschluss eingeschlossen, um {2 > 1}ihn zu werden, bevor er an übergeben wird f. Wenn wir dies also auf die Funktion anwenden f:

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

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

Es funktioniert also nur mit einem Ausdruck, ohne ihn in einen Verschluss einwickeln zu müssen.

eddie_c
quelle
2
Eigentlich funktioniert der letzte nicht. Es sollte seinf({2 >1}())
Rui Peres
@JoelFischer Ich sehe das Gleiche wie @JackyBoy. Anrufen f(2 > 1)funktioniert. Anruf f({2 > 1})schlägt fehl mit error: function produces expected type 'Bool'; did you mean to call it with '()'?. Ich habe es auf einem Spielplatz und mit dem Swift REPL getestet.
Ole Begemann
Ich habe irgendwie die vorletzte Antwort als letzte Antwort gelesen, ich muss es noch einmal überprüfen, aber es wäre sinnvoll, wenn es fehlschlagen würde, da Sie im Grunde genommen einen Verschluss in einen Verschluss stecken, soweit ich weiß.
Joel Fischer
3
Es gibt einen Blog-Beitrag über den Grund, warum sie das getan haben. developer.apple.com/swift/blog/?id=4
mohamed-ted
5
Tolle Erklärung. Beachten Sie auch, dass in Swift 1.2 'Autoclosure' jetzt ein Attribut der Parameterdeklaration ist, also ist esfunc f(@autoclosure pred: () -> Bool)
Masa
30

Hier ist ein praktisches Beispiel - mein printOverride (dies ist Swift 3):

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

Wenn Sie sagen print(myExpensiveFunction()), printüberschattet mein Override Swifts printund wird aufgerufen. myExpensiveFunction()wird somit in einen Verschluss eingewickelt und nicht ausgewertet . Wenn wir uns im Release-Modus befinden, wird es nie ausgewertet, da item()es nicht aufgerufen wird. Daher haben wir eine Version print, die ihre Argumente im Release-Modus nicht auswertet.

matt
quelle
Ich bin zu spät zur Party, aber wie wirkt sich die Bewertung aus myExpensiveFunction()? Wenn Sie anstelle der automatischen Schließung die Funktion zum Drucken übergeben print(myExpensiveFunction), welche Auswirkungen hätte dies? Vielen Dank.
Crom87
11

Beschreibung von auto_closure aus den Dokumenten:

Sie können das Attribut auto_closure auf einen Funktionstyp anwenden, der den Parametertyp () hat und den Typ eines Ausdrucks zurückgibt (siehe Typattribute). Eine Autoclosure-Funktion erfasst einen impliziten Closure über dem angegebenen Ausdruck anstelle des Ausdrucks selbst. Im folgenden Beispiel wird das Attribut auto_closure zum Definieren einer sehr einfachen Assert-Funktion verwendet:

Und hier ist das Beispiel, das Apple dazu verwendet.

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

Grundsätzlich bedeutet dies, dass Sie einen booleschen Ausdruck als erstes Argument anstelle eines Abschlusses übergeben und daraus automatisch einen Abschluss für Sie erstellen. Aus diesem Grund können Sie false an die Methode übergeben, da es sich um einen booleschen Ausdruck handelt, aber keinen Abschluss übergeben kann.

Connor
quelle
15
Beachten Sie, dass Sie @auto_closurehier eigentlich nicht verwenden müssen . Der Code funktioniert gut ohne : func simpleAssert(condition: Bool, message: String) { if !condition { println(message) } }. Verwenden @auto_closureSie diese Option, wenn Sie ein Argument wiederholt auswerten müssen (z. B. wenn Sie eine whileähnliche Funktion implementiert haben ) oder wenn Sie die Auswertung eines Arguments verzögern müssen (z. B. wenn Sie einen Kurzschluss implementieren &&).
Nathan
1
@ Nathan Hallo Nathan. Könnten Sie mir bitte ein Beispiel für die Verwendung autoclosuremit einer whileähnlichen Funktion nennen? Ich scheine das nicht herauszufinden. Vielen Dank im Voraus.
Unheilig
@connor Vielleicht möchten Sie Ihre Antwort für Swift 3 aktualisieren.
Jarora
4

Dies zeigt einen nützlichen Fall von @autoclosure https://airspeedvelocity.net/2014/06/28/extending-the-swift-language-is-cool-but-be-careful/

Jetzt wird der bedingte Ausdruck, der als erster Parameter an bis übergeben wurde, automatisch in einen Abschlussausdruck eingeschlossen und kann jedes Mal in der Schleife aufgerufen werden

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

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

Es ist nur eine Möglichkeit, die geschweiften Klammern in einem Closure Call loszuwerden, einfaches Beispiel:

    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
quelle
0

@autoclosureist ein Funktionsparameter, der eine gekochte Funktion (oder einen zurückgegebenen Typ) closureakzeptiert, während ein General eine Rohfunktion akzeptiert

  • Der Parameter für den Argumenttyp @autoclosure muss '()' sein.
    @autoclosure ()
  • @autoclosure akzeptiert alle Funktionen mit nur dem entsprechenden zurückgegebenen Typ
  • Das Ergebnis der Schließung wird nach Bedarf berechnet

Schauen wir uns ein Beispiel an

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
quelle