Que sont les Generics en Swift et pourquoi en avons-nous besoin
Les Generics (programmation générique) sont l'une des fonctionnalités les plus puissantes du langage Swift. Ils permettent d'écrire un code flexible et réutilisable qui fonctionne avec n'importe quel type de données, tout en conservant un typage strict. Au lieu de créer des fonctions ou des structures séparées pour Int, String, Double et d'autres types, vous écrivez un code universel qui s'adapte au type spécifique lors de la compilation.
Imaginez que vous devez écrire une fonction qui échange deux valeurs. Sans Generics, vous devriez créer des surcharges pour chaque type :
func swapInts(_ a: inout Int, _ b: inout Int) { let temp = a a = b b = temp}
func swapStrings(_ a: inout String, _ b: inout String) { let temp = a a = b b = temp}Avec les Generics, cela se fait en une seule ligne :
func swapValues<T>(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp}
var x = 10, y = 20swapValues(&x, &y) // fonctionne avec Int
var str1 = "Hello", str2 = "World"swapValues(&str1, &str2) // fonctionne avec StringPrincipaux avantages des Generics
- Sécurité des types : le compilateur vérifie les types lors de la compilation, éliminant les erreurs d'exécution
- Réutilisation du code : un code écrit une fois fonctionne avec n'importe quel type
- Performance : les Generics en Swift sont implémentés au niveau du compilateur, sans perte de vitesse d'exécution
- Lisibilité : réduit la duplication de code, le rend plus concis
Fonctions génériques et paramètres de type
Une fonction générique est déclarée avec des chevrons <>, à l'intérieur desquels est spécifié un paramètre de type (généralement désigné par la lettre T, mais vous pouvez utiliser n'importe quel nom). Le paramètre de type devient un placeholder qui est remplacé par le type réel lors de l'appel de la fonction.
Exemple : recherche d'un élément dans un tableau
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? { for (index, element) in array.enumerated() { if element == value { return index } } return nil}
let numbers = [1, 5, 3, 12, 7]if let index = findIndex(of: 12, in: numbers) { print("Index du nombre 12 : \\(index)") // Index du nombre 12 : 3}
let names = ["Anna", "Boris", "Victor"]if let index = findIndex(of: "Boris", in: names) { print("Index du prénom Boris : \\(index)") // Index du prénom Boris : 1}Notez la contrainte T: Equatable. C'est une type constraint — nous exigeons que le type T supporte le protocole Equatable, sinon l'opération de comparaison == serait impossible.
Types génériques : structures, classes et énumérations
Les Generics fonctionnent non seulement avec les fonctions, mais aussi avec les types personnalisés. Cela permet de créer des conteneurs de données universels.
Création d'une pile générique
Un exemple classique est la structure de données Stack (pile), fonctionnant avec n'importe quel type :
struct Stack<Element> { private var items: [Element] = [] mutating func push(_ item: Element) { items.append(item) } @discardableResult mutating func pop() -> Element? { return items.popLast() } func peek() -> Element? { return items.last } var isEmpty: Bool { return items.isEmpty }}
// Utilisationvar intStack = Stack<Int>()intStack.push(10)intStack.push(20)print(intStack.pop() ?? "") // 20
var stringStack = Stack<String>()stringStack.push("Swift")stringStack.push("Generics")print(stringStack.pop() ?? "") // GenericsÉnumérations génériques
Les énumérations peuvent également être