Generic (programmation générique) — est un mécanisme de Swift permettant d’écrire des types et des fonctions avec un type paramétré
Points Clés
Generic (type générique) — est une construction syntaxique de Swift qui permet d’écrire du code flexible et réutilisable avec paramétrage de type. Au lieu d’un type concret, un espace réservé est écrit entre crochets angulaires — généralement
Sans génériques, il faudrait écrire des fonctions séparées pour chaque type : swapInts, swapStrings, swapDates. Une fonction générique swap remplace les trois variantes par une seule déclaration. Cela réduit la duplication, améliore la lisibilité et diminue le risque d’erreurs lors de la copie de code.
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 10
var y = 20
swapValues(&x, &y)
Le compilateur Swift génère du code spécialisé pour chaque utilisation concrète d’un générique. Cela signifie que la fonction générique swapValues
Fonctions génériques sont des fonctions qui acceptent un ou plusieurs paramètres de type. Les paramètres de type sont spécifiés après le nom de la fonction entre crochets angulaires :
Une fonction peut avoir plusieurs paramètres génériques. Par exemple, la fonction findKey
func findKey<K: Hashable, V>(
for value: V,
in dictionary: [K: V]
) -> K? where V: Equatable {
for (key, dictValue) in dictionary {
if dictValue == value {
return key
}
}
return nil
}
Les contraintes K: Hashable et where V: Equatable garantissent que findKey ne peut être appelée qu’avec un dictionnaire dont les clés sont hashable et les valeurs equatable. Ces contraintes ne sont pas de la bureaucratie mais une nécessité : sans Hashable, on ne peut pas indexer une recherche par clé, sans Equatable, on ne peut pas comparer value avec dictValue.
where — est un mot-clé de Swift qui impose des contraintes supplémentaires aux paramètres génériques. Contrairement à la simple spécification d’un protocole entre crochets angulaires (
La clause where est écrite après la signature de la fonction ou du type, avant l’accolade ouvrante. Dans where, vous pouvez spécifier que T: Comparable & Hashable (conforme à deux protocoles simultanément), ou que T.U == Int (concrétiser un associated type).
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
}
extension Container where Item: Comparable {
func isSorted() -> Bool {
// Implementation available only if Item: Comparable
return true
}
}
L’extension avec where ajoute des méthodes uniquement pour les types qui satisfont la condition. Container
Associated Types (types associés) — sont une façon de rendre un protocole générique sans spécifier de type concret au moment de la déclaration. Au lieu de
En utilisant where, vous pouvez établir des relations entre les associated types de différents protocoles. Par exemple, vous pouvez exiger que l’Item d’un protocole corresponde à Iterator.Element d’un autre. Cela garantit la compatibilité des types au niveau du compilateur.
protocol StackProtocol {
associatedtype Element
mutating func push(_ item: Element)
mutating func pop() -> Element?
}
struct IntStack: StackProtocol {
typealias Element = Int
private var items: [Int] = []
mutating func push(_ item: Int) { items.append(item) }
mutating func pop() -> Int? { items.popLast() }
}
IntStack concrétise Element comme Int en utilisant typealias. Le compilateur vérifie que toutes les exigences du protocole sont remplies avec ce type. Sans associated types, il faudrait écrire StackProtocol
Extensions génériques permettent d’ajouter des méthodes à un type générique avec des contraintes supplémentaires. C’est le modèle de « conformité conditionnelle », où un type obtient des fonctionnalités seulement sous certaines conditions. Les subscripts peuvent également être génériques.
Un subscript peut être générique : il accepte des paramètres génériques entre crochets angulaires. Ceci est utile pour un accès sécurisé aux collections avec des index arbitraires, où l’index peut être de différents types.
extension Array where Element: Numeric {
func sum() -> Element {
return reduce(0, +)
}
}
extension Array {
subscript<Indices: Sequence>(indices: Indices) -> [Element]
where Indices.Element == Int {
return indices.map { self[$0] }
}
}
Cette construction offre une composition puissante : la méthode sum apparaît uniquement sur les tableaux numériques, et le subscript avec des index arbitraires fonctionne avec toute séquence d’index entiers. Le compilateur gère la visibilité de ces membres en fonction des conditions where.
Erreurs lors de l’utilisation des génériques sont souvent liées à des contraintes incorrectes ou à la tentative d’utiliser des génériques là où un type concret est nécessaire. Examinons trois scénarios courants rencontrés par les développeurs.
Ajouter des paramètres génériques là où un type concret suffit est un anti-modèle. Si une fonction travaille toujours avec String, il n’est pas nécessaire de la rendre
Appeler une méthode sur un paramètre générique sans spécifier que la méthode existe est une erreur.
// ❌ Error: value has no count method
func countElements<T>>(value: T) -> Int { value.count }
// ✅ Works: T: Collection guarantees count exists
func countElements<T: Collection>(value: T) -> Int { value.count }
L’erreur est claire : le générique
Questions Fréquentes
Generic est une façon d’écrire du code qui fonctionne avec n’importe quel type, sans dupliquer la même logique pour Int, String et d’autres types. Au lieu d’un type concret, un espace réservé
Generic préserve les informations sur le type concret au moment de la compilation, permettant au compilateur de vérifier les types et de générer du code optimisé. Any est un effacement de type : n’importe quelle valeur peut être passée, mais le type est perdu, et un cast (as?) est nécessaire.
Utilisez la syntaxe T: SomeProtocol entre crochets angulaires ou une clause where séparée where T: SomeProtocol. Les contraintes garantissent que le paramètre générique prend en charge des méthodes et propriétés spécifiques.
Associated Type (associatedtype) est un espace réservé pour un type à l’intérieur d’un protocole. Le protocole ne spécifie pas un type concret mais déclare un associatedtype, qui est concrétisé dans l’implémentation : Array a Element, Dictionary a Key et Value.
Swift compile le code générique en versions spécialisées pour chaque type concret. En pratique, une fonction générique pour Int n’est pas plus lente qu’une fonction écrite à la main pour Int. Il n’y a pas de surcharge d’abstraction.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi