Swift @escaping et gestionnaire d'achèvement

99

J'essaie de comprendre plus précisément la «fermeture» de Swift.

Mais @escapinget Completion Handlersont trop difficiles à comprendre

J'ai recherché de nombreux messages et documents officiels Swift, mais je pensais que ce n'était toujours pas suffisant.

Ceci est l'exemple de code des documents officiels

var completionHandlers: [()->Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping ()->Void){
    completionHandlers.append(completionHandler)
}

func someFunctionWithNoneescapingClosure(closure: ()->Void){
    closure()
}

class SomeClass{
    var x:Int = 10
    func doSomething(){
        someFunctionWithEscapingClosure {
            self.x = 100
            //not excute yet
        }
        someFunctionWithNoneescapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)

completionHandlers.first?() 
print(instance.x)

J'ai entendu dire qu'il y a deux façons et raisons d'utiliser @escaping

Le premier est pour stocker une fermeture, le second est pour le fonctionnement Async.

Voici mes questions :

Premièrement, si doSomethings'exécute, alors someFunctionWithEscapingClosures'exécutera avec le paramètre de fermeture et cette fermeture sera enregistrée dans un tableau de variables globales.

Je pense que la clôture est {self.x = 100}

Comment selfdans {self.x = 100} qui enregistré dans la variable globale completionHandlerspeut se connecter à instancecet objet de SomeClass?

Deuxièmement, je comprends someFunctionWithEscapingClosurecomme ça.

Pour stocker la fermeture de variable locale completionHandlerdans le we usingmot-clé de la variable globale 'completionHandlers @ escaping`!

sans retour de @escapingmot-clé someFunctionWithEscapingClosure, la variable locale completionHandlersupprimera de la mémoire

@escaping est de garder cette fermeture dans la mémoire

Est-ce correct?

Enfin, je m'interroge simplement sur l'existence de cette grammaire.

C'est peut-être une question très rudimentaire.

Si nous voulons qu'une fonction s'exécute après une fonction spécifique. Pourquoi n'appelons-nous pas simplement une fonction après un appel de fonction spécifique?

Quelles sont les différences entre l'utilisation du modèle ci-dessus et l'utilisation d'une fonction de rappel d'échappement?

Dongkun Lee
la source

Réponses:

123

Échappement et non échappement du gestionnaire d'achèvement rapide:

Comme Bob Lee l'explique dans son article de blog Completion Handlers in Swift with Bob :

Supposons que l'utilisateur met à jour une application tout en l'utilisant. Vous voulez absolument avertir l'utilisateur lorsque cela est fait. Vous voudrez peut-être faire apparaître une boîte qui dit: "Félicitations, maintenant, vous pouvez en profiter pleinement!"

Alors, comment exécuter un bloc de code uniquement une fois le téléchargement terminé? De plus, comment animer certains objets uniquement après qu'un contrôleur de vue a été déplacé vers le suivant? Eh bien, nous allons découvrir comment en concevoir un comme un patron.

Sur la base de ma longue liste de vocabulaire, les gestionnaires de complétion représentent

Faites des choses quand les choses ont été faites

Le message de Bob fournit des éclaircissements sur les gestionnaires d'achèvement (du point de vue du développeur, il définit exactement ce que nous devons comprendre).

@ fermetures échappées:

Quand on passe une fermeture dans les arguments de la fonction, l'utiliser après que le corps de la fonction soit exécuté et renvoie le compilateur. Lorsque la fonction se termine, la portée de la fermeture passée existe et existe en mémoire, jusqu'à ce que la fermeture soit exécutée.

Il existe plusieurs façons d'échapper à la fermeture dans la fonction contenant:

  • Stockage: lorsque vous avez besoin de stocker la fermeture dans la variable globale, la propriété ou tout autre stockage existant dans la mémoire de la fonction appelante est exécutée et renvoie le compilateur.

  • Exécution asynchrone: lorsque vous exécutez la fermeture de manière asynchrone sur la file d'attente d'expédition, la file d'attente conservera la fermeture en mémoire pour vous, peut être utilisée à l'avenir. Dans ce cas, vous ne savez pas quand la fermeture sera exécutée.

Lorsque vous essayez d'utiliser la fermeture dans ces scénarios, le compilateur Swift affichera l'erreur:

capture d'écran d'erreur

Pour plus de clarté sur ce sujet, vous pouvez consulter cet article sur Medium .

Ajout d'un point de plus, que chaque développeur iOS doit comprendre:

  1. Fermeture d'échappement : une fermeture d'échappement est une fermeture appelée après la fonction à laquelle elle a été transmise. En d'autres termes, il survit à la fonction à laquelle il a été transmis.
  2. Fermeture sans échappement : Une fermeture appelée dans la fonction dans laquelle elle a été passée, c'est-à-dire avant son retour.
Shobhakar Tiwari
la source
@shabhakar, que faire si nous stockons une fermeture mais ne l'appelons pas plus tard. Ou si la méthode a été appelée deux fois mais que nous n'avons appelé la fermeture qu'une seule fois. Puisque nous savons que le résultat est le même.
user1101733
@ user1101733 Je pense que vous parlez d'échapper à la fermeture, la fermeture ne s'exécutera pas tant que vous n'appelerez pas. Dans l'exemple ci-dessus, si l'appel de la méthode doSomething 2 fois 2, l'objet completionHandler s'ajoutera au tableau completionHandlers. Si vous prenez le premier objet du tableau de completionHandlers et que vous l'appelez, il s'exécutera mais le nombre de tableaux de completionHandlers restera le même (2).
Deepak
@Deepak, Oui à propos de la fermeture de l'évasion. supposons que nous n'utilisons pas de tableau et que nous utilisions une variable normale pour stocker la référence de fermeture puisque nous quoi exécuter l'appel le plus récent. Y aura-t-il de la mémoire occupée par des fermetures précédentes qui n'appelleront jamais?
user1101733
1
@ user1101733 Les fermetures sont de type référence (comme la classe), lorsque vous affectez de nouvelles fermetures à la variable, la propriété / la variable pointera vers une nouvelle fermeture afin que l'ARC désalloue la mémoire pour les fermetures précédentes.
Deepak
28

Voici une petite classe d'exemples que j'utilise pour me rappeler comment fonctionne @escaping.

class EscapingExamples: NSObject {

    var closure: (() -> Void)?

    func storageExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because `closure` is outside the scope of this
        //function - it's a class-instance level variable - and so it could be called by any other method at
        //any time, even after this function has completed. We need to tell `completion` that it may remain in memory, i.e. `escape` the scope of this
        //function.
        closure = completion
        //Run some function that may call `closure` at some point, but not necessary for the error to show up.
        //runOperation()
    }

    func asyncExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because the completion closure may be called at any time
        //due to the async nature of the call which precedes/encloses it.  We need to tell `completion` that it should
        //stay in memory, i.e.`escape` the scope of this function.
        DispatchQueue.global().async {
            completion()
        }
    }

    func asyncExample2(with completion: (() -> Void)) {
        //The same as the above method - the compiler sees the `@escaping` nature of the
        //closure required by `runAsyncTask()` and tells us we need to allow our own completion
        //closure to be @escaping too. `runAsyncTask`'s completion block will be retained in memory until
        //it is executed, so our completion closure must explicitly do the same.
        runAsyncTask {
            completion()
        }
    }





    func runAsyncTask(completion: @escaping (() -> Void)) {
        DispatchQueue.global().async {
            completion()
        }
    }

}
JamesK
la source
2
Ce code n'est pas correct. Il manque les @escapingqualifications.
Rob
J'ai le plus aiméi.e. escape the scope of this function.
Gal