Paramètre de fermeture d'échappement optionnel Swift

162

Donné:

typealias Action = () -> ()

var action: Action = { }

func doStuff(stuff: String, completion: @escaping Action) {
    print(stuff)
    action = completion
    completion()
}

func doStuffAgain() {
    print("again")
    action()
}

doStuff(stuff: "do stuff") { 
    print("swift 3!")
}

doStuffAgain()

Existe-t-il un moyen de rendre le completionparamètre (et action) de type Action?et de le conserver également @escaping?

Changer le type donne l'erreur suivante:

L'attribut @escaping s'applique uniquement aux types de fonction

En supprimant l' @escapingattribut, le code se compile et s'exécute, mais ne semble pas être correct car la completionfermeture échappe à la portée de la fonction.

Lescai Ionel
la source
21
"Suppression de l' @escapingattribut, le code se compile et s'exécute" - C'est parce que, comme décrit dans SR-2444 , Action?est, par défaut, échappement. Ainsi, la suppression @escapinglors de l'utilisation de la fermeture optionnelle accomplit ce dont vous avez besoin.
Rob du
les fermetures d'alias de type s'échappent
Masih
Voici un excellent article d'Ole Begemann qui décrit pourquoi cela se produit et quelques solutions de contournement si vous voulez que les paramètres facultatifs soient @noescape.
Senseful

Réponses:

122

Il existe un rapport SR-2552 qui @escapingne reconnaît pas l'alias de type de fonction. c'est pourquoi l'erreur @escaping attribute only applies to function types. vous pouvez contourner le problème en développant le type de fonction dans la signature de fonction:

typealias Action = () -> ()

var action: Action? = { }

func doStuff(stuff: String, completion: (@escaping ()->())?) {
    print(stuff)
    action = completion
    completion?()
}

func doStuffAgain() {
    print("again")
    action?()
}

doStuff(stuff: "do stuff") {
    print("swift 3!")
}

doStuffAgain()

MODIFIER 1 ::

J'étais en fait sous une version bêta de xcode 8 où le bogue SR-2552 n'était pas encore résolu. correction de ce bogue, en a introduit un nouveau (celui auquel vous êtes confronté) qui est toujours ouvert. voir SR-2444 .

La solution de contournement indiquée par @Michael Ilseman comme une solution temporaire consiste à supprimer l' @escapingattribut du type de fonction facultatif, qui garde la fonction en échappement .

func doStuff(stuff: String, completion: Action?) {...}

MODIFIER 2 ::

Le SR-2444 a été fermé en indiquant explicitement que la fermeture des postes de paramètres ne sont pas échapper et doivent être marqués les avec @escapingpour les faire échapper, mais les paramètres facultatifs sont échappaient implicitement, car ((Int)->())?est un synonymes de Optional<(Int)->()>, fermetures en option fuient.

Jans
la source
5
Maintenant@escaping may only be applied to parameters of function type func doStuff(stuff: String, completion: (@escaping ()->())?) {
Lescai Ionel
1
a temporary solution is remove the @escaping attribute from optional function type, that keep the function as escaping. Pouvez-vous expliquer cela davantage? La sémantique par défaut dans swift 3 est sans échappement. Bien qu'il se compile sans @escaping, j'ai peur que cela pose des problèmes en étant traité comme non-échappant. N'est-ce pas vrai?
Pat Niemeyer
49
En lisant plus loin, je vois que SR-2444 dit que toutes les fermetures optionnelles sont traitées comme des échappements, ce qui est un bogue complémentaire :) Je vais supposer que lorsqu'elle sera corrigée, la compilation nous avertira de faire le changement.
Pat Niemeyer
Peut-être un peu hors sujet; mais comment ça marche @autoclosure? On obtient la même erreur là-bas ...
Gee.E
ça marche sans @escaping __ func doStuff (stuff: String, completion: (() -> ())?) {
Феннур Мезитов
226

de: liste de diffusion swift-users

Fondamentalement, @escaping n'est valide que sur les fermetures en position de paramètre de fonction. La règle noescape-by-default s'applique uniquement à ces fermetures à la position du paramètre de fonction, sinon elles s'échappent. Les agrégats, tels que les énumérations avec les valeurs associées (par exemple, facultatif), les tuples, les structs, etc., s'ils ont des fermetures, suivent les règles par défaut pour les fermetures qui ne sont pas à la position du paramètre de fonction, c'est-à-dire qu'elles s'échappent.

Le paramètre de fonction facultatif est donc @escaping par défaut.
@noeascape s'applique uniquement au paramètre de fonction par défaut.

Dmitry Coolerov
la source
7
Je pense que cela ajoute les informations les plus importantes au sujet, il devrait être une réponse acceptée.
Damian Dudycz
La réponse raisonnée. Quelle est la probabilité que cela puisse changer?
GoldenJoe
2
Cela a du sens car, techniquement, dire équivaut à dire (()->Void)?que vous avez Optional<()->Void>et pour Optionalconserver la propriété, il ne devrait accepter que des @escapingfonctions. Je troisième que cela devrait être la réponse acceptée. Je vous remercie.
Dean Kelly
22

J'ai rencontré un problème similaire parce que mélanger @escapinget non @escapingest très déroutant, surtout si vous devez passer les fermetures.

J'ai fini par attribuer une valeur par défaut sans opération au paramètre de fermeture via = { _ in }, ce qui, à mon avis, a plus de sens:

func doStuff(stuff: String = "do stuff",
        completion: @escaping (_ some: String) -> Void = { _ in }) {
     completion(stuff)
}

doStuff(stuff: "bla") {
    stuff in
    print(stuff)
}

doStuff() {
    stuff in
    print(stuff)
}
Homme libre
la source
C'est propre et joli dans la mise en œuvre.
Fred Faust
2
Et si je veux vérifier si ce bloc est nul / vide?
Vyachaslav Gerchicov
2
Bummer, cela ne fonctionne pas pour une méthode de protocole. "Argument par défaut non autorisé dans une méthode de protocole" (Xcode 8.3.2).
Mike Taverne
17

Je l'ai fait fonctionner dans Swift 3 sans aucun avertissement uniquement de cette façon:

func doStuff(stuff: String, completion: (()->())? ) {
    print(stuff)
    action = completion
    completion?()
}
Igor
la source
4

La chose importante à comprendre dans l'exemple est que si vous changez Actionà Action?la fermeture est s'échapper. Alors, faisons ce que vous proposez:

typealias Action = () -> ()

var action: Action? = { }

func doStuff(stuff: String, completion: Action?) {
    print(stuff)
    action = completion
    completion?()
}

OK, maintenant nous allons appeler doStuff:

class ViewController: UIViewController {
    var prop = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        doStuff(stuff: "do stuff") {
            print("swift 3!")
            print(prop) // error: Reference to property 'prop' in closure 
                        // requires explicit 'self.' to make capture semantics explicit
        }
    }
}

Eh bien, cette exigence ne se pose que pour échapper aux fermetures. La fermeture s'échappe donc. C'est pourquoi vous ne marquez pas qu'il s'échappe - il s'échappe déjà.

mat
la source