Introduction : que sont les Generics et pourquoi sont-ils importants en Swift
Les Generics (types génériques) sont l'une des fonctionnalités les plus puissantes et élégantes 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. Si vous avez déjà utilisé Array<Int>, Dictionary<String, Any> ou Optional<T> — vous avez déjà rencontré les Generics.
Dans cet article, nous allons voir pourquoi les Generics sont nécessaires en Swift, comment ils fonctionnent en pratique, et montrer des exemples pour le développement iOS/macOS. Ce contenu sera utile aussi bien aux débutants qu'aux développeurs expérimentés souhaitant structurer leurs connaissances.
1. Pourquoi les Generics sont-ils nécessaires en Swift ?
L'objectif principal des Generics est d'éviter la duplication de code et d'améliorer sa sécurité. Imaginez que vous devez écrire une fonction qui échange deux valeurs. Sans Generics, vous devriez créer des fonctions séparées pour Int, String, Double, etc. :
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, cette tâche est résolue par une seule fonction universelle :
func swapValues<T>(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp}
var x = 10, y = 20swapValues(&x, &y) // x = 20, y = 10
var name1 = "Alice", name2 = "Bob"swapValues(&name1, &name2) // name1 = "Bob", name2 = "Alice"Avantages des Generics :
- Réutilisation du code : une seule fonction/type fonctionne avec n'importe quel type de données.
- Sécurité de typage : le compilateur vérifie les types à la compilation, éliminant les erreurs d'exécution.
- Performance : les Generics en Swift sont implémentés via le polymorphisme au moment de la compilation (spécialisation), donc il n'y a pas de surcoût lié au cast de types.
- Lisibilité : le code devient plus abstrait et concis.
2. Fonctions génériques : syntaxe et exemples
Une fonction générique est déclarée avec des chevrons <T>, où T est un placeholder (espace réservé) pour le type. Vous pouvez utiliser n'importe quel identifiant : T, Element, Value, etc.
Exemple de base : recherche de l'index d'un élément
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? { for (index, item) in array.enumerated() { if item == value { return index } } return nil}
let numbers = [1, 3, 5, 7, 9]if let index = findIndex(of: 5, in: numbers) { print("Index du nombre 5 : \\(index)") // Index du nombre 5 : 2}Notez la contrainte <T: Equatable>. C'est une type constraint (contrainte de type). Elle indique au compilateur : "T doit être conforme au protocole Equatable". Sans cette contrainte, l'opérateur == ne serait pas disponible.
Paramètres de type multiples
Vous pouvez utiliser plusieurs paramètres de type :
func combine<T, U>(_ first: T, with second: U) -> String { return "\\(first) + \\(second)"}
let result = combine(42, with: "réponse")print(result) // 42 + réponse3. Types génériques : création de structures et classes génériques
Les Generics s'appliquent non seulement aux fonctions, mais aussi aux types. L'exemple le plus populaire est Array<Element>. Créons notre propre structure générique — une pile (LIFO) :
struct Stack<Element> { private var items: [Element] = [] mutating func push(_ item: Element) { items.append(item