Si tu es un développeur(euse), tu as sûrement déjà entendu parler des types génériques. Si tu ne les connais pas encore, c’est le moment de t’y intéresser. Les types génériques peuvent sembler intimidants au premier abord, mais ils sont en réalité assez simples à comprendre et peuvent grandement améliorer ton code.
Grâce au code générique, il est possible d’écrire des fonctions et des types flexibles et réutilisables qui peuvent être utilisés avec n’importe quel type. Ce type de code permet d’éviter la duplication de code inutile. Et c’est d’ailleurs comme ça qu’est construite une grande partie de la bibliothèque de Swift !
En effet, tu peux créer un tableau contenant des Int
, ou un tableau contenant des String, ou encore un tableau pour tout autre type pouvant être créé en Swift…
Fonctionnement des types génériques en Swift
Afin de donner un type « unique », on symbolise le type générique avec la lettre T
généralement. Par exemple avec cette fonction ;
struct Resultat<T> {
let equipe: T
let score: Int
}
func afficherResultats<T>(_ resultats: [Resultat<T>]) {
for resultat in resultats {
print("\(resultat.equipe) : \(resultat.score)")
}
}
On créé ici une structure basée sur un type générique. Le paramètre equipe
peut être soit un joueur, soit un nom, soit autre chose (puisque c’est générique). La fonction afficherResultats
prend en paramètre un tableau de résultats de type Resultat<T>
et qui affiche les équipes et leurs scores.
let resultatsBasketball = [
Resultat(equipe: "Équipe A", score: 78),
Resultat(equipe: "Équipe B", score: 64),
Resultat(equipe: "Équipe C", score: 89)
]
afficherResultats(resultatsBasketball)
// Équipe A : 78
// Équipe B : 64
// Équipe C : 89
Ici, on comptabilise les résultats des équipes de BasketBall, mais on pourrait faire de même avec les équipes de Football ou de Curling (qui sait ?).
Voilà toute la beauté du type générique, il peut être appliqué à n’importe quoi !
• Contrainte de paramètre générique
Un paramètre générique peut être suivi d’une contrainte et peut être appelé T
, U
, V
, Key
, Value
, Element
, etc.
La contrainte spécifie qu’un paramètre de type hérite d’une classe spécifique ou se conforme à un protocole. Par exemple, ici, on utilise le protocole Comparable
afin que la fonction puisse comparer les éléments du tableau et les trier. Il est utilisé pour définir une relation d’ordre entre deux valeurs d’un même type.
func trier<T: Comparable>(_ tableau: [T]) -> [T] {
return tableau.sorted()
}
• Clause where
On peut spécifier d’autres exigences grâce à la clause where
.
func printElements<T>(array: [T]) where T: CustomStringConvertible {
for element in array {
print(element)
}
}
let numbers = [1, 2, 3]
let strings = ["un", "deux", "trois"]
printElements(array: numbers)
printElements(array: strings)
Dans cet exemple, la clause where
est utilisée pour imposer une contrainte sur T
: il doit adopter le protocole CustomStringConvertible
. Cette méthode permet de convertir une instance en une chaîne de caractères. En imposant cette contrainte, nous pouvons utiliser la méthode description
de l’instance de T
pour l’afficher dans la fonction printElements
.
Tu peux également spécifier que deux types doivent être identiques, en utilisant l’opérateur ==
, pour deux éléments de même type.
func areEqual<T>(first: T, second: T) -> Bool where T: Equatable {
return first == second
}
let number1 = 5
let number2 = 10
let string1 = "hello"
let string2 = "world"
print(areEqual(first: number1, second: number2)) // false
print(areEqual(first: string1, second: string2)) // false
print(areEqual(first: number1, second: number1)) // true
print(areEqual(first: string1, second: "hello")) // true
Le protocole Equatable
permet de comparer deux instances d’un même type en utilisant l’opérateur ==
. En imposant cette contrainte, nous pouvons utiliser l’opérateur ==
pour comparer les éléments passés en paramètre dans la fonction areEqual
.
Limites des types génériques en Swift
Bien qu’ils aient de nombreux avantages, ils ont aussi quelques limites :
- Ils ne peuvent pas être utilisés pour stocker des types primitifs tels que les entiers ou les booléens. Ils sont uniquement utilisés sur des types définis par l’utilisateur tels que les classes, les structures et les énumérations
- Les types génériques peuvent être plus difficiles à comprendre que les types spécifiques
- Ils peuvent entraîner une complexité accrue du code. Si les types génériques ne sont pas utilisés correctement, cela peut rendre le code plus difficile à maintenir et à debbuger.
- Les types génériques peuvent être moins efficaces que les types spécifiques en termes de performances (à cause de la nécessité de l’utilisation de la liaison dynamique)
Comme beaucoup d’outils puissants en dev, l’utilisation des types génériques doit être réfléchie et adaptée aux besoins du projet.
Tu veux en savoir plus sur les types génériques ? Voici le lien de la documentation officielle.