web-dev-qa-db-fra.com

Fuite des fermetures à Swift

Je suis nouveau à Swift et je lisais le manuel lorsque je suis tombé sur une fermeture échappée. Je n'ai pas eu la description du manuel du tout. Quelqu'un pourrait-il m'expliquer, en termes simples, ce que sont les fermetures à éviter de Swift.

38
Nikhil Sridhar

Considérez cette classe:

class A {
    var closure: (() -> Void)?
    func someMethod(closure: () -> Void) {
        self.closure = closure
    }
}

someMethod assigne la fermeture transmise à une propriété de la classe.

Maintenant, voici une autre classe:

class B {
    var number = 0
    var a: A = A()
    func anotherMethod() {
        a.someMethod { self.number = 10 }
    }
}

Si j'appelle anotherMethod, la fermeture { self.number = 10 } sera stockée dans l'instance de A. Puisque self est capturé dans la fermeture, l'instance de A y fera également référence.

C'est fondamentalement un exemple de fermeture échappée!

Vous vous demandez probablement, "quoi? Alors, d'où vient la fermeture de la fermeture?"

La fermeture échappe du domaine de la méthode à celui de la classe. Et on peut l'appeler plus tard, même sur un autre thread! Cela pourrait causer des problèmes s’il n’est pas traité correctement.

Pour éviter les fuites accidentelles de fermetures et les cycles de rétention et autres problèmes, utilisez l'attribut @noescape:

class A {
    var closure: (() -> Void)?
    func someMethod(@noescape closure: () -> Void) {
    }
}

Maintenant, si vous essayez d'écrire self.closure = closure, il ne compile pas!

Mettre à jour:

Dans Swift 3, tous les paramètres de fermeture ne peuvent pas s'échapper par défaut. Vous devez ajouter l'attribut @escaping pour que la fermeture puisse sortir de l'étendue actuelle. Cela ajoute beaucoup plus de sécurité à votre code!

class A {
    var closure: (() -> Void)?
    func someMethod(closure: @escaping () -> Void) {
    }
}
65
Sweeper

Je vais d'une manière plus simple.

Considérons cet exemple:

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

Ce qui précède est une fermeture qui ne s'échappe pas parce que la fermeture est invoqué avant le retour de la méthode.

Prenons le même exemple avec une opération asynchrone:

func testFunctionWithEscapingClosure(closure:@escaping () -> Void) {
      DispatchQueue.main.async {
           closure()
      }
 }

L'exemple ci-dessus contient une fermeture d'échappement, car l'appel de fermeture peut survenir après le retour de la fonction en raison de l'opération asynchrone.

 var completionHandlers: [() -> Void] = []
 func testFunctionWithEscapingClosure(closure: @escaping () -> Void) {
      completionHandlers.append(closure)
 }

Dans le cas ci-dessus, vous pouvez facilement vous rendre compte que la fermeture se déplace vers l’extérieur corps de la fonction donc il doit être une fermeture échappée.

La fermeture d'échappement et la fermeture non d'échappement ont été ajoutées pour optimiser le compilateur dans Swift 3. Vous pouvez rechercher les avantages de la fermeture nonescaping.

39
LC 웃

Je trouve ce site très utile à cet égard Une explication simple serait: 

Si une fermeture est transmise en tant qu'argument à une fonction et qu'elle est invoquée après le retour de la fonction, la fermeture s'échappe.

Lire la suite sur le lien que j'ai passé ci-dessus! :)

14
stan

Swift 4.1

De Référence du langage: Attributs du langage de programmation Swift (Swift 4.1)

Apple explique clairement l'attribut escaping.

Appliquez cet attribut au type de paramètre dans une déclaration de méthode ou de fonction pour indiquer que la valeur du paramètre peut être stockée pour une exécution ultérieure. Cela signifie que la valeur est autorisée à survivre à la durée de vie de l'appel. Les paramètres de type de fonction avec l'attribut de type d'échappement nécessitent l'utilisation explicite de self. pour les propriétés ou les méthodes. Pour un exemple d'utilisation de l'attribut d'échappement, voir Fermeture d'échappement

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

La fonction someFunctionWithEscapingClosure(_:) prend une fermeture comme argument et l’ajoute à un tableau déclaré en dehors de la fonction. Si vous n’avez pas marqué le paramètre de cette fonction avec @escaping, vous obtiendrez une erreur lors de la compilation.

On dit qu'une fermeture échappe à une fonction lorsque la fermeture est transmise en tant qu'argument à la fonction, mais qu'elle est appelée après le retour de la fonction. Lorsque vous déclarez une fonction qui prend une fermeture parmi ses paramètres, vous pouvez écrire @escaping avant le type du paramètre pour indiquer que la fermeture est autorisée à sortir.

3
black_pearl

Par défaut, les fermetures ne s'échappent pas. Pour une compréhension simple, vous pouvez considérer les fermetures non-d'échappement comme une fermeture locale (tout comme les variables locales) et l'échappement comme une fermeture globale (tout comme les variables globales). Cela signifie que lorsque nous sortons du corps de la méthode, l'étendue de la fermeture non décapante est perdue. Mais en cas de fermeture par échappement, la mémoire a conservé la fermeture dans la mémoire.

*** Simplement, nous utilisons l'échappement de la fermeture lorsque nous appelons la fermeture dans une tâche asynchrone de la méthode, ou que la méthode retourne avant d'appeler la fermeture.

Fermeture non décapante: -  

func add(num1: Int, num2: Int, completion: ((Int) -> (Void))) -> Int {
    DispatchQueue.global(qos: .background).async {
        print("Background")
        completion(num1 + num2) // Error: Closure use of non-escaping parameter 'completion' may allow it to escape
    }
    return num1
}

override func viewDidLoad() {
    super.viewDidLoad()
    let ans = add(num1: 12, num2: 22, completion: { (number) in
        print("Inside Closure")
        print(number)
    })
    print("Ans = \(ans)")
    initialSetup()
}

Puisqu'il s'agit d'une fermeture non décapante, sa portée sera perdue lorsque nous sortirons de la méthode 'add'. achèvement (num1 + num2) ne sera jamais appeler.

Fermeture d'échappement: -  

func add(num1: Int, num2: Int, completion: @escaping((Int) -> (Void))) -> Int {
    DispatchQueue.global(qos: .background).async {
        print("Background")
        completion(num1 + num2)
    }
    return num1
}

Même si la méthode retourne (c'est-à-dire que nous sortons de la portée de la méthode), la fermeture sera appelée .enter code here

0
DEEPAK KUMAR