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

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.


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.


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.


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()
}

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

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()
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.