Comment utiliser Swift @autoclosure

148

J'ai remarqué lors de l'écriture d'un assertSwift que la première valeur est tapée comme

@autoclosure() -> Bool

avec une méthode surchargée pour renvoyer une Tvaleur générique , pour tester l'existence via le LogicValue protocol.

Cependant s'en tenir strictement à la question à l'étude. Il semble vouloir un @autoclosurequi renvoie un Bool.

L'écriture d'une fermeture réelle qui ne prend aucun paramètre et retourne un booléen ne fonctionne pas, cela veut que j'appelle la fermeture pour la faire compiler, comme ceci:

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

Cependant, le simple fait de passer un Bool fonctionne:

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

Alors, quoi de neuf? C'est quoi @autoclosure?

Edit: a @auto_closure été renommé@autoclosure

Joël Fischer
la source

Réponses:

269

Prenons une fonction qui prend un argument, une simple fermeture qui ne prend aucun argument:

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

Pour appeler cette fonction, il faut passer une fermeture

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

Si nous omettons les accolades, nous passons une expression et c'est une erreur:

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

@autoclosurecrée une fermeture automatique autour de l'expression. Ainsi, lorsque l'appelant écrit une expression comme 2 > 1, elle est automatiquement enveloppée dans une fermeture pour devenir {2 > 1}avant qu'elle ne soit transmise f. Donc, si nous appliquons ceci à la fonction f:

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

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

Cela fonctionne donc avec juste une expression sans avoir besoin de l'envelopper dans une fermeture.

eddie_c
la source
2
En fait, le dernier ne fonctionne pas. Ça devrait êtref({2 >1}())
Rui Peres
@JoelFischer Je vois la même chose que @JackyBoy. L'appel f(2 > 1)fonctionne. L'appel f({2 > 1})échoue avec error: function produces expected type 'Bool'; did you mean to call it with '()'?. Je l'ai testé dans une aire de jeux et avec le Swift REPL.
Ole Begemann
J'ai en quelque sorte lu l'avant-dernière réponse comme la dernière réponse, je vais devoir vérifier, mais cela aurait du sens si elle échouait, car vous mettez essentiellement une fermeture dans une fermeture, d'après ce que je comprends.
Joel Fischer
3
il y a un article de blog sur la raison pour laquelle ils ont fait cela developer.apple.com/swift/blog/?id=4
mohamed-ted
5
Excellente explication. Notez également que dans Swift 1.2 'autoclosure' est maintenant un attribut de la déclaration de paramètre, donc c'estfunc f(@autoclosure pred: () -> Bool)
Masa
30

Voici un exemple pratique - mon printremplacement (c'est Swift 3):

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

Quand vous dites print(myExpensiveFunction()), ma printpriorité éclipse celle de Swift printet est appelée. myExpensiveFunction()est ainsi enveloppé dans une fermeture et non évalué . Si nous sommes en mode Release, il ne sera jamais évalué, car item()il ne sera pas appelé. Nous avons donc une version de printqui n'évalue pas ses arguments en mode Release.

mat
la source
Je suis en retard à la fête, mais quel est l'impact de l'évaluation myExpensiveFunction()?. Si au lieu d'utiliser l'autoclosure vous transmettez la fonction pour imprimer comme print(myExpensiveFunction), quel en serait l'impact? Merci.
crom87
11

Description de auto_closure dans la documentation:

Vous pouvez appliquer l'attribut auto_closure à un type de fonction qui a un type de paramètre () et qui renvoie le type d'une expression (voir Attributs de type). Une fonction de fermeture automatique capture une fermeture implicite sur l'expression spécifiée, au lieu de l'expression elle-même. L'exemple suivant utilise l'attribut auto_closure pour définir une fonction d'assertion très simple:

Et voici l'exemple que Apple utilise avec lui.

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

Fondamentalement, cela signifie que vous passez une expression booléenne comme premier argument au lieu d'une fermeture et que cela crée automatiquement une fermeture pour vous. C'est pourquoi vous pouvez passer false dans la méthode car il s'agit d'une expression booléenne, mais ne peut pas passer de fermeture.

Connor
la source
15
Notez que vous n'avez pas besoin de l'utiliser @auto_closureici. Le code fonctionne très bien sans elle: func simpleAssert(condition: Bool, message: String) { if !condition { println(message) } }. À utiliser @auto_closurelorsque vous devez évaluer un argument à plusieurs reprises (par exemple, si vous implémentez une whilefonction semblable à celle-ci) ou lorsque vous devez retarder l'évaluation d'un argument (par exemple, si vous implémentez un court-circuit &&).
nathan
1
@nathan Salut, nathan. Pourriez-vous s'il vous plaît me citer un exemple concernant l'utilisation de autoclosureavec une whilefonction semblable à celle-ci? Je ne semble pas comprendre cela. Merci d'avance.
Unheilig
@connor Vous voudrez peut-être mettre à jour votre réponse pour Swift 3.
jarora
4

Cela montre un cas utile de @autoclosure https://airspeedvelocity.net/2014/06/28/extending-the-swift-language-is-cool-but-be-careful/

Désormais, l'expression conditionnelle passée comme premier paramètre à jusqu'à sera automatiquement enveloppée dans une expression de fermeture et peut être appelée à chaque fois autour de la boucle

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

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

C'est juste un moyen de se débarrasser des accolades lors d'un appel de fermeture, exemple simple:

    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
Policier
la source
0

@autoclosureest un paramètre de fonction qui accepte une fonction cuisinée (ou un type retourné) tandis qu'un général closureaccepte une fonction brute

  • Le paramètre de type d'argument @autoclosure doit être '()'
    @autoclosure ()
  • @autoclosure accepte toute fonction avec uniquement le type de retour approprié
  • Le résultat de la fermeture est calculé par demande

Jetons un coup d'œil à l'exemple

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
la source