La généricité en Swift

Une des fonctionnalités les plus puissantes introduite dans Swift est la généricité (generics en anglais). La généricité du code permet d’écrite du code flexible, réutilisable tout en maintenant une vérification des types. Encore une fois la généricité est utilisé pour éviter la duplication de code et fournir un niveau d’abstraction de haut niveau.

La problématique

Pour comprendre la généricité rien ne vaut un petit example mis en contexte. Le plus simple en swift quand on parle de généricité est de s’intéresser aux collections. Nous voulons définir une liste d’élément quelconque :

var liste = ["Swift Tuto", 14, 3.2, CGPoint.zero]

Ici nous avons une liste avec des éléments de type disparate et nous pouvons ajouter n’importe quoi comme élément à cette liste :

liste.append(UIColor.redColor())

Ici Swift infère en réalité le type générique AnyObject ce qui implique que nous devons manuellement typer les objets de cette liste :

for elem in liste {
  if let color = elem as? UIColor {
    // ...
  }
  else if entier = elem as? Int {
    // ...
  }
  // ...
}

Maintenant imaginons que nous voulions une liste qui ne contient que des entiers et être sûr qu’il n’y a que des entiers. Nous pourrions faire ça en code au moment où l’on rajoute les éléments à la liste mais cela n’empêchera pas d’éventuelles erreurs. Vous l’aurez compris c’est là que les génériques interviennent. Nous allons « dire » à la liste qu’elle ne peut contenir que des entiers :

var listeEntier = Array()
// équivalent à
var listeEntier: [Int] = []

Nous précisons que le type d’objet que peut contenir notre listeEntier sont des Int. Il est alors très simple de parcourir une liste sans se soucier du type qu’il contient :

for entier in listeEntier {
  // entier est de type Int
}

Maintenant essayons d’ajouter des éléments :

Erreur avec les générics en swift sous Xcode

L’avantage principale des generics comme vous pouvez le voir est sa capacité à vérifier les contraintes de typage à la compilation et donc d’éviter toute erreur lors de l’exécution du programme.

Maintenant prenons un cas où l’utilisation d’un AnyObject ou Any ne fonctionne pas. Pour cela prenons cet example qui intervertis 2 chaines de caractères :

func swapTwoStrings(inout a: String, inout b: String) {
  let temporaryA = a

  a = b
  b = temporaryA
}

Remplaçons les types String avec le type Any :

func swapTwo(inout a: Any, inout b: Any) {
  let temporaryA = a

  a = b
  b = temporaryA
}

Tout semble fonctionner ici mais en réalité si nous utiliser 2 types différents en paramètre (par example un String et un Int) le programme va planter à l’exécution. En effet ici on est pas capable de tester que les 2 types en paramètre sont de même type et pour le compilateur il n’y a aucune erreur. Dans ces cas là, vous l’aurez encore compris, les génériques sont très utiles.

La généricité

Pour rendre la méthode de notre example précédent générique et résoudre notre problème nous allons écrire ceci :

func swapTwo<T>(inout a: T, inout b: T) {
  let temporaryA = a

  a = b
  b = temporaryA
}

La principale différence au premier coup d’oeil est la présence de chevron <> ansi que le type T. Tout d’abord le type T est arbitraire on aurait pu utiliser X, Y ou tout autre chaine de caractère qui ne correspond pas à un type déjà existant. En ensuite les chevrons sont là pour dire que c’est une fonction générique utilisant le type T. Ce type T étant totalement indéfini ici on peut utiliser n’importe quoi mais surtout on remarque que a et b sont tout les deux de type T. Donc ici on a réussi à mettre une contrainte sur les types de tel sorte que a et b ne peuvent pas être différent. Et cela dès la compilation :

var a = "Swift"
var b = "Tuto"

swapTwo(&a, b: &b) // Tout est OK

Erreur avec les génériques en swift sous Xcode

 

Nous pouvons aussi définir des fonctions qui utilisent plus d’un type générique. Par example nous pourrions avoir une méthode comme ça :

func uneFonction<T, U>(a: T, b: U) {}

Ici nous avons définis 2 types réservés: T et U. Quand la fonction est appelé avec T et U spécifié, a doit être une instance de T et b une instance de U. Par example ceci doit échouer :

uneFonction<String, Int>(1, "Test")

Être capable d’utiliser des types très générique tel que Any ou AnyObject est certainement un avantage mais nous avons aussi souvent besoin d’utiliser des types plus spécifiques pour faire tourner nos algorithmes.

Les contraintes de type

Nous pouvons allez un peu plus loin avec nos générique en ajoutant des contraintes. Le premier type de contrainte est la spécification de l’héritage ou de la conformité d’un protocole sur un type générique.

Imaginons que l’on a une application qui vend différent produit. Dans ce magasin on a des livres, films et des musiques que l’on peut définir dans une classe Media qui hérite de Produit. Maintenant nous pouvons définir une fonction générique qui trie uniquement les Media selon différents critères comme par example le style.

Voici un example signature de méthode correspondant :

func trierParStyle<T: Media>(collection: [T]) {
  // ...
}

Cette fonction générique prend comme paramètre une liste de type T qui lui est contraint aux types héritant de Media :

let livres: [Livre] = [...]

// Ok
func trierParStyle(collection: livres)

// Non Ok
let habits: [Vetement] = [...]

func trierParStyle(collection: habits) // Erreur

La contrainte sur l’héritage permet de s’assurer des objets qui seront passé en paramètre comme ici avec le deuxième example.

La deuxième contrainte que l’on peut appliquer sur les générique se fait avec la clause where. Cette clause permet de définir plusieurs contraintes d’un coup. Reprenons l’example du trie des média, ajoutons comme contrainte que nous voulons uniquement des médias français. Pour cela nous avons un protocole OrigineFrance qui garanti cela :

func trierParStyle<T where T: Media, T: OrigineFrance>(collection: [T]) {

Nous ne pouvons que vous conseiller la documentation officielle pour voir toutes les possibilités offerte par la clause where

Les types génériques

La généricité ne s’applique pas qu’aux méthodes ou fonction (du moins en Swift), mais aussi aux class, struct et aux enum. Cela permet de créer des types ou des objets génériques.

Par example les Array et les Dictionary sont des types génériques.

Définissons un objet qui contient une liste d’entier et qui nous permet de piocher un de ces entier aléatoirement :

struct RandomIntBag {
  let bag: [Int]
    
  func getRandom() -> Int? {
    guard bag.count > 0 else { return nil }

    return bag[Int(arc4random_uniform(UInt32(bag.count)))]
  }
}

Que se passe t’il si à la place d’un entier nous voulions utiliser avoir uniquement des String ? Sans généricité il nous faudrait créer un nouvel objet RandomStringBag qui aurait exactement la même structure. Mais grâce à cet capacité du langage nous allons pouvoir écrire du code beaucoup plus élégant :

struct RandomBag {
  let bag: [T]
    
  func getRandom() -> T? {
    guard bag.count > 0 else { return nil }

    return bag[Int(arc4random_uniform(UInt32(bag.count)))]
  }
}

Voila ! Maintenant vous pouvez aussi bien créer un sac de String que de Int ou bien de tout autre chose et ce sans réécrire à chaque fois la même chose :

let intBag = RandomBag(bag: [1, 5, 7, 10])
print(intBag.getRandom()) // 7

let stringBag = RandomBag(bag: ["Swift", "Objective-C", "Scala", "Haskell"])
print(stringBag.getRandom()) // Swift

En dépit d’être un exemple trivial, cela donne un bon aperçu de la façon dont les génériques peuvent aider à réduire le code et améliorer la sécurité de vos programmes.

Conclusion

Dans ce cours nous nous sommes concentré sur la généricité en Swift. Nous avons appris comment ce mécanisme fonctionne et exploré comment les utiliser dans différents cas comme les fonctions, les classes et les structures. Nous avons aussi appris comment contraindre les génériques avec les protocoles et l’héritage en utilisant la clause where.

Avec une bonne compréhension des génériques, vous devriez être capable d’écrire du code plus réutilisable et résoudre des problèmes plus difficile.

1 Etoile2 Etoiles3 Etoiles4 Etoiles5 Etoiles (2 votes, average: 5,00 out of 5)
Loading...

Aucun commentaire

Annuler

Time limit is exhausted. Please reload CAPTCHA.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.