Pourquoi sans génériques votre code Swift se transformera bientôt en spaghetti
\n\nImaginez que vous écrivez une application de livraison de pizzas. Vous devez traiter les commandes, les clients, les livreurs — et à chaque fois écrire le même code pour le tri, la recherche ou le filtrage, en changeant uniquement le type de données ? Cela ressemble au cauchemar de tout développeur. C'est ici qu'entrent en scène les Generics en Swift — un outil puissant qui permet d'écrire un code flexible et réutilisable sans sacrifier la sécurité des types.
\n\nDans cet article, nous ne nous contenterons pas de survoler la syntaxe. Nous analyserons comment le typage en Swift et les génériques fonctionnent ensemble pour que votre code soit non seulement beau, mais aussi fiable. Vous apprendrez à vous débarrasser du copier-coller, à utiliser protocol avec associatedtype et pourquoi les clauses where sont vos meilleures amies.
Que sont les Generics et pourquoi changent-ils les règles du jeu
\n\nLes Generics (génériques, types génériques) sont un mécanisme qui permet d'écrire des fonctions, structures, classes et énumérations qui fonctionnent avec n'importe quel type de données satisfaisant certaines conditions. Au lieu d'écrire trois versions d'une fonction pour Int, String et Double, vous en écrivez une qui fonctionne avec n'importe quel type.
En Swift, le typage est strict et statique. Cela signifie que le compilateur vérifie les types au moment de la compilation. Les Generics ne violent pas cette règle — ils la rendent plus flexible. Le compilateur déduit lui-même le type concret lors de l'appel de la fonction, mais les vérifications restent.
\n\nPremier exemple : fonction sans génériques
\n\n// Mauvais : trois fonctions pour trois types\nfunc swapInts(_ a: inout Int, _ b: inout Int) {\n let temp = a\n a = b\n b = temp\n}\n\nfunc swapStrings(_ a: inout String, _ b: inout String) {\n let temp = a\n a = b\n b = temp\n}\n\nfunc swapDoubles(_ a: inout Double, _ b: inout Double) {\n let temp = a\n a = b\n b = temp\n}\n\nEt maintenant — une solution élégante avec des génériques :
\n\n// Bien : une fonction pour tous les types\nfunc swapValues<T>(_ a: inout T, _ b: inout T) {\n let temp = a\n a = b\n b = temp\n}\n\n// Utilisation :\nvar x = 10\nvar y = 20\nswapValues(&x, &y) // T = Int\n\nvar name1 = "Alice"\nvar name2 = "Bob"\nswapValues(&name1, &name2) // T = String\n\nVous voyez la magie ? <T> est un placeholder (espace réservé) de type. Le compilateur substitue lui-même le type réel lors de l'appel. C'est la base de la programmation générique en Swift.
Types génériques : créons notre propre pile universelle
\n\nLes génériques en Swift fonctionnent non seulement avec les fonctions, mais aussi avec des types entiers. Créons une structure Stack — un exemple classique de structure de données LIFO (Last In, First Out).
struct Stack<Element> {\n private var items: [Element] = []\n \n mutating func push(_ item: Element) {\n items.append(item)\n }\n \n mutating func pop() -> Element? {\n return items.popLast()\n }\n \n func peek() -> Element? {\n return items.last\n }\n \n var count: Int {\n return items.count\n }\n}\n\n// Utilisation :\nvar intStack = Stack<Int>()\nintStack.push(1)\nintStack.push(2)\nintStack.push(3)\nprint(intStack.pop() ?? 0) // 3\n\nvar stringStack = Stack<String>()\nstringStack.push("Swift")\nstringStack.push("Generics")\nprint(stringStack.peek() ?? "") // "Generics"\n\nRemarquez : le type Element est spécifié entre chevrons lors de la déclaration de la structure. Lors de la création d'une instance, nous le concrétisons — Stack<Int> ou Stack<String>. Le compilateur garantit que vous ne pouvez pas accidentellement mettre une chaîne dans une pile d'entiers.
Protocol avec associatedtype : génériques pour les protocoles
\n\nEn Swift, les protocoles ne peuvent pas avoir de paramètres génériques directement. À la place, on utilise