Revenir

Entre ton email pour recevoir le guide complet pour apprendre Swift peu importe ton niveau

Tout sur les closures en Swift

Si vous ne savez pas ce que sont les closures, tu en as probablement déjà utilisé sans même le savoir.

Qu’est-ce que les closures

Pour faire simple, les closures c’est quand on a une fonction en tant que variable.

Par exemple, vous avez peut-être déjà vu ça :

fonction(param1: valeur) {
    // completion handler
}

Ça, c’est une forme contractée pour passer une fonction en 2ème paramètre de la fonction comme si c’était une variable.

Le 2ème paramètre de la fonction sera donc une closure.

Comment les utiliser

Juste avant, je vous ai montré un exemple de comment les passer en paramètre d’une fonction.

Maintenant, on va voir comment créer des fonctions qui prennent en paramètre une closure :

func getData(params: String, completion: () -> ()) {
    // on fait des trucs...
    completion() // on lance le code passé en paramètres
}

À quoi ça sert ?

À plein de choses, mais principalement à attendre que du code long soit éxecuté avant de continuer.

Un code long, c’est un code qui doit attendre quelque chose.

Par exemple, quand tu fais des requêtes API, ton code doit attendre une réponse du serveur et donc c’est du code long.

On appelle ça des trailing closures.

Il y a cependant un cas particulier.

Il arrive que la fonction soit appelée après que la fonction ait fini d’être éxecutée.

Par exemple si tu stockes dans une variable définie en dehors de la fonction qui contient la closure.

À ce moment là, il faudra marquer la closure avec le mot-clé @escaping :

func getData(params: String, completion: @escaping () -> ()) {
    // on fait des trucs...
    completion() // on lance le code passé en paramètres
}

Les paramètres et les retours dans les closures

Avec ce qu’on a fait on peut faire pas mal de choses mais comme dans toute fonction, il faudrait pouvoir prendre des paramètres et retourner des choses!

Pour faire ça, il suffit de changer le type de la closure :

func getData(params: String, completion: (String) -> ()) {
    // on fait des trucs
    completion("Hello") // on appelle la closure en lui passant un paramètre
}

getData(params: "") { (data) in
    print(data) // on affiche le paramètre passé dans la closure
}

Dans notre exemple, on a une closure qui prend 1 paramètre.

Ensuite, dans l’appelle de la fonction on va créer notre closure d’une façon différente de ce qu’on a pu faire car on a un paramètre.

Avec une valeur de retour ça donnerait :

func getData(params: String, completion: (String) -> (String)) {
    // on fait des trucs
    completion("Hello")
}

let data = getData(params: "") { (data) in
    return data
}

Les capture list

Les closures sont des fonctions un peu spéciales car elles ont un environnement à part de la classe dans laquelle est définie la fonction qui l’utilise.

Vous avez probablement déjà vu que dans les closures, on ne peut pas accéder aux valeurs de la classe dans laquelle on est sans mettre le mot-clé self.

C’est parce-que la closure ne connaît pas la classe dans laquelle on est.

Mais on a quand même pas besoin de prendre en paramètre self dans notre closure alors comment ça se fait qu’on y a accès ?

C’est grâce aux capture list.

Le self est passé dans la capture list implicitement.

Les capture list permettent de prendre des variables externes à la fonction en paramètre.

Toutes les valeurs externes que vous utilisez dans la closures sont capturées automatiquement.

Pour définir la capture list, on fait comme ça :

UIView.animate(withDuration: 1) { [unowned self] in
    self.view.backgroundColor = .red
}

Il y a plusieurs types de capture de variable.

Ici, on utilise le type unowned mais on a aussi weak et strong.

Par défaut, c’est strong automatiquement.

Différence strong, weak et unowned

C’est un concept assez compliqué alors suis bien.

Imaginons qu’on ait la fonction suivante :

func getSayHelloFunction() -> () -> Void {
    let message = "Hello"

    let sayHello = {
        print(message)
        return
    }

    return sayHello
}

let sayHello = getSayHelloFunction()
sayHello()

Avec ce code, la closure sayHello capture la variable message pour pouvoir l’utiliser.

Sans le strong comme type de capture, la variable message aurait été détruite après l’appel de la fonction.

Et donc quand on aurait fait sayHello() on aurait eu une erreur car la variable message aurait été supprimée.

Mais ça peut créer des problèmes de mémoire.

Car les variables de capturées avec strong sont stockées tout le temps que l’application tourne et ça peut consommer beaucoup de ressources si vous avez beaucoup de variables comme ça.

Ou pire, si une variable strong A utilise une autre variable B qui elle même utilise A, votre app peut planter, on appelle ça un retain cycle.

C’est pourquoi on a inventé weak et unowned.

Avec weak, on laisse les variables se supprimer et donc elle seront potentiellement nil quand on essaiera d’y accéder.

Ce qui fait que toutes les variables capturées avec weak sont des optionnels.

C’est assez peu pratique que ça soit des optionnels c’est pourquoi on a créé unowned.

En gros, unowned fait la même chose que weak sauf que vous pourrez utiliser votre variable comme si ce n’était pas un optionnel.

Pas mal non?

Conclusion

J’ai essayé de faire en sorte de prendre le temps de bien expliquer ce concept compliqué pour les débutants.

Maintenant, c’est avec la pratique que vous comprendrez le mieux.

Vous allez les voir partout et c’est une très bonne chose et vous alez vite très bien comprendre si c’est encore un peu flou.