Jak używać Swift @autoclosure

148

Pisząc assertw Swift zauważyłem, że pierwsza wartość jest wpisana jako

@autoclosure() -> Bool

z przeciążoną metodą w celu zwrócenia Twartości ogólnej , aby przetestować istnienie za pośrednictwem LogicValue protocol.

Jednak trzymając się ściśle pytania. Wygląda na to, @autoclosureże chce otrzymać, który zwraca a Bool.

Pisanie rzeczywistego zamknięcia, które nie przyjmuje parametrów i zwraca Bool, nie działa, chce, żebym wywołał zamknięcie, aby je skompilować, na przykład:

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

Jednak proste przekazanie Bool działa:

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

Więc, co się dzieje? Co to jest @autoclosure?

Edycja: @auto_closure została zmieniona@autoclosure

Joel Fischer
źródło

Odpowiedzi:

269

Rozważmy funkcję, która przyjmuje jeden argument, proste zamknięcie, które nie przyjmuje żadnego argumentu:

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

Aby wywołać tę funkcję, musimy przekazać zamknięcie

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

Jeśli pominiemy nawiasy klamrowe, przekazujemy wyrażenie i to jest błąd:

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

@autoclosuretworzy automatyczne zamknięcie wokół wyrażenia. Więc kiedy wywołujący zapisuje wyrażenie takie jak 2 > 1, jest automatycznie zawijane w zamknięcie, aby stać się {2 > 1}nim zostanie przekazane do f. Więc jeśli zastosujemy to do funkcji f:

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

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

Więc działa tylko z wyrażeniem, bez potrzeby zawijania go w zamknięcie.

eddie_c
źródło
2
Właściwie ten ostatni nie działa. Powinno byćf({2 >1}())
Rui Peres
@JoelFischer Widzę to samo co @JackyBoy. Dzwonienie f(2 > 1)działa. Połączenie f({2 > 1})kończy się niepowodzeniem error: function produces expected type 'Bool'; did you mean to call it with '()'?. Przetestowałem to na placu zabaw i za pomocą Swift REPL.
Ole Begemann
W jakiś sposób przeczytałem przedostatnią odpowiedź jako ostatnią odpowiedź, będę musiał dwukrotnie sprawdzić, ale miałoby to sens, gdyby się nie udało, ponieważ zasadniczo zamykasz zamknięcie w zamknięciu, z tego, co rozumiem.
Joel Fischer
3
na blogu jest wpis na temat powodu, dla którego to zrobili developer.apple.com/swift/blog/?id=4
mohamed-ted.
5
Świetne wyjaśnienie. Należy również zauważyć, że w Swift 1.2 „autoclosure” jest teraz atrybutem deklaracji parametru, więc jest tofunc f(@autoclosure pred: () -> Bool)
Masa
30

Oto praktyczny przykład - moje printnadpisanie (to jest Swift 3):

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

Kiedy mówisz print(myExpensiveFunction()), moje printnadpisanie przesłania Swifta printi jest wywoływane. myExpensiveFunction()jest zatem zapakowany w zamknięcie i nie jest oceniany . Jeśli jesteśmy w trybie wydania, nigdy nie zostanie to ocenione, ponieważ item()nie zostanie wywołane. Tak więc mamy wersję print, która nie ocenia swoich argumentów w trybie wydania.

matowe
źródło
Spóźniłem się na imprezę, ale jaki wpływ ma ocena myExpensiveFunction()? Jeśli zamiast używać autoclosure, przekażesz funkcję do drukowania print(myExpensiveFunction), jaki będzie wpływ? Dzięki.
crom87
11

Opis auto_closure z dokumentacji:

Możesz zastosować atrybut auto_closure do typu funkcji, który ma typ parametru () i który zwraca typ wyrażenia (zobacz Atrybuty typu). Funkcja autoclosure przechwytuje niejawne zamknięcie określonego wyrażenia zamiast samego wyrażenia. Poniższy przykład używa atrybutu auto_closure przy definiowaniu bardzo prostej funkcji assert:

A oto przykład, którego używa jabłko wraz z nim.

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

Zasadniczo oznacza to, że przekazujesz wyrażenie boolowskie jako pierwszy argument zamiast domknięcia i automatycznie tworzy ono za Ciebie zamknięcie. Dlatego możesz przekazać do metody false, ponieważ jest to wyrażenie boolowskie, ale nie może przekazać zamknięcia.

Connor
źródło
15
Zauważ, że tak naprawdę nie musisz @auto_closuretutaj używać . Kod działa poprawnie bez niego: func simpleAssert(condition: Bool, message: String) { if !condition { println(message) } }. Użyj, @auto_closuregdy musisz wielokrotnie oceniać argument (np. Jeśli implementowałeś funkcję whilepodobną do-) lub musisz opóźnić ocenę argumentu (np. Jeśli implementujesz zwarcie &&).
Nathan
1
@nathan Cześć, nathan. Czy mógłbyś zacytować mi próbkę dotyczącą użycia autoclosurez whilepodobną funkcją? Nie wydaje mi się, żeby to rozumieć. Z góry bardzo dziękuję.
Unheilig
@connor Możesz zaktualizować swoją odpowiedź dla Swift 3.
jarora
4

To pokazuje przydatny przypadek @autoclosure https://airspeedvelocity.net/2014/06/28/extending-the-swift-language-is-cool-but-be-careful/

Teraz wyrażenie warunkowe przekazane jako pierwszy parametr do aż zostanie automatycznie opakowane w wyrażenie zamykające i może być wywoływane za każdym razem w pętli

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

// doSomething until condition becomes true
until(condition) {
    doSomething()
}
onmyway133
źródło
2

To tylko sposób na pozbycie się nawiasów klamrowych w rozmowie zamykającej, prosty przykład:

    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
Konstabl
źródło
0

@autoclosurejest parametrem funkcji, który akceptuje gotową funkcję (lub zwracany typ), podczas gdy generał closureakceptuje funkcję surową

  • Parametr typu argumentu @autoclosure musi mieć wartość „()”
    @autoclosure ()
  • @autoclosure akceptuje każdą funkcję z tylko odpowiednim zwracanym typem
  • Wynik zamknięcia jest obliczany według zapotrzebowania

Spójrzmy na przykład

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
źródło