Swift 2.0 a introduit 2 nouvelles structures de control qui ont pour objectif de simplifier et rationaliser le code que nous écrivons : guard
et defer
. Tandis que le premier rend le code plus linéaire par nature, le deuxième permet de reporter l’exécution de son contenu. Dans ce tutoriel nous allons voir comment utiliser ces 2 nouvelles structures et dans quels cas ils sont utile pour améliorer la lisibilité et la compréhension du code.
Guard
Swift 1.2 avait réglé le problème de l’imbrication des if-let
en introduisant la syntaxe du chainage multiple des optionnels. Avec Swift 2.0, guard
supprime complètement le problème.
Cette nouvelle structure de control nécessite l’exécution à sortir du bloc de code courant si la condition n’est pas satisfaite. Ce qui est intéressant avec guard
c’est que toutes liaisons optionnelles (Optional Binding) créer dans la condition sont disponibles dans l’ensemble du bloc de code courant :
for imageName in imageNames {
guard let image = UIImage(named: imageName) else {
continue
}
// Vous pouvez utiliser image
}
Le bloc de code après le else
doit obligatoirement quitter le contexte courant en utilisant soit return
pour quitter une méthode, continue
ou break
a l’intérieur d’une boucle, ou une fonction @noreturn
comme fatalError()
.
Maintenant regardons un avant/après sur la manière dont guard
peut améliorer notre code et empêcher certaines erreurs. Comme example nous allons créer une méthode qui va récupérer des informations sur liste d’utilisateur que l’on passe en paramètre. Cette méthode peut émettre des erreurs si il n’y a aucune connection de disponible, si l’on est pas connecté au service distant ou si tout simplement on ne lui passe pas d’utilisateur à chercher. Voici l’example sans utiliser de guard
:
func chercherClients(clients: [Client]?) throws {
if !accessible {
throw APIError.APIErrorUnreachable
}
if !connecté {
throw APIError.APIErrorNotConnected
}
if let clients = clients where clients.count > 0 {
print(clients)
}
else {
throw APIError.APIErrorNoCustomers
}
}
Maintenant regardons ce que l’utilisation de guard
apporte :
func chercherClients(clients: [Client]?) throws {
guard !accessible else { throw APIError.APIErrorUnreachable }
guard !connecté else { throw APIError.APIErrorNotConnected }
guard let clients = clients where clients.count > 0 else {
throw APIError.APIErrorNoCustomers
}
print(clients)
}
Beaucoup mieux. Maintenant chaque erreur est traiter dès qu’elle a été vérifier et nous savons en voyons le mot clé guard
que le code peut potentiellement s’arrêter à cet endroit. De plus on évite ici le dernier if-let
qui imbrique notre code et le rend donc moins lisible.
Defer
Entre la structure du guard
et celle du throw
pour la gestion des erreurs, Swift 2.0 semble encourager un certain style de retourner/quitter le plus tôt possible contrairement aux structures if imbriqués. Cependant retourner le plus tôt possible engendre de nouvelle problématique comme le fait qu’une fois des resources initialisé (et en utilisation) nous devons les nettoyer avant de sortir de la méthode.
Le nouveau mot clé defer
fournit un moyen simple et sûre de répondre a ce problème en déclarant un bloc de code qui sera exécuter seulement quand l’exécution quitte le bloc de code courant. Prenons un example d’une fonction qui ouvre un fichier pour l’afficher :
func afficherFichier(nomDuFichier: String) throws {
if let fichier = ouvrirFichier(nomDuFichier) {
while let ligne = try fichier.ligneSuivante() {
print(ligne)
}
fermerFichier(fichier)
}
}
La problématique ici, c’est que si une erreur est levée durant l’exécution de la méthode ligneSuivante
la méthode fermerFichier
ne sera jamais fermé (ou alors il faut traiter le cas dans la méthode appelant afficherFichier
).
Voici comment defer
et guard
viennent à la rescousse :
func afficherFichier(nomDuFichier: String) throws {
guard let fichier = ouvrirFichier(nomDuFichier) else { return }
defer {
fermerFichier(fichier)
}
while let ligne = try fichier.ligneSuivante() {
print(ligne)
}
}
La méthode fermerFichier
va être exécuter uniquement une fois que l’on aura fini de lire toute les lignes. Même si une erreur survient pendant la lecture des lignes le bloc de code dans defer sera dans tout les cas appelé. On est certain alors que le fichier sera correctement fermé. Cela nous permet d’éviter certains effet de bord et de rendre ainsi notre code plus sûre.
Pour finir, une chose importante à connaitre sur defer
. Les blocs de code defer
sont exécutés dans le sens inverse de leur déclaration. Par example :
func testDefer() {
defer { print("A") }
defer { print("B") }
defer { print("C") }
}
// C, B, A
testDefer()
Cette ordre inverse est un détail essentiel, car il permet d’assurer que tout ce qui était à la portée du bloc lors de sa création le sera toujours lorsque le bloc est exécuté.
Conclusion
Un grand pouvoir implique de grandes responsabilités, il faut peser les avantages de chaque option du langage par rapport à ses coûts. Une nouvelle déclaration comme guard
conduit à un code plus linéaire, un programme plus lisible et tend à être appliquer aussi largement que possible. De même, defer
résout un défi de taille, mais nous oblige à garder une trace de sa déclaration comme il peut être hors de porté de la vue; et donc il faut l’utiliser avec réserve.
Comme d’habitude si vous avez des commentaires, n’hésitez pas.
Très bonne explication =) continue je passerais régulièrement voir les nouveaux articles =)
Merci beaucoup pour ces encouragements, je compte bien continuer.