Les paramètres génériques configurables par l'utilisateur sont apparus dans les protocoles Swift avec le mot-clé associatedtype depuis la première version de Swift, afin de donner un meilleur contrôle sur la typage lors de l'abstraction du comportement.
Si un protocole déclare une exigence où un certain type doit être défini (par exemple, le type des éléments dans un conteneur) — sans associatedtype, il devient impossible d'utiliser le protocole avec abstraction : les types doivent être fixés de manière rigide, ce qui fait perdre la possibilité d'écrire du code universel.
associatedtype permet de déclarer de manière déclarative dans le protocole un type associé, qui est défini par une implémentation spécifique du protocole. Cela permet d'écrire des protocoles et des fonctions génériques sans connaître les types spécifiques au moment de la compilation.
Exemple de protocole avec associatedtype :
protocol MyContainer { associatedtype Item var count: Int { get } mutating func append(_ item: Item) subscript(i: Int) -> Item { get } } struct IntStack: MyContainer { var items = [Int]() mutating func append(_ item: Int) { items.append(item) } var count: Int { items.count } subscript(i: Int) -> Int { items[i] } // Swift déduit que Item = Int }
Points clés :
let x: MyContainer // erreur),Pourquoi associatedtype est-il nécessaire s'il existe déjà des génériques en Swift ?
Réponse : Les génériques et associatedtype résolvent des problèmes similaires mais différents. Le paramètre générique s'applique à un type ou une fonction, permettant de créer des structures et méthodes généralisées. L'associatedtype est une abstraction à l'intérieur du protocole, qui impose aux types implémentants de définir leur propre type. Utilisez les génériques pour des algorithmes universels ou des conteneurs, et associatedtype pour le comportement via des protocoles.
Peut-on utiliser un protocole avec associatedtype comme type de variable ?
Non, Swift ne permet pas d'utiliser de tels protocoles comme type directement, car l'information sur associatedtype est perdue. Pour abstraction, on peut faire du type erase ou utiliser une contrainte générique :
func printElements<C: MyContainer>(container: C) { for i in 0..<container.count { print(container[i]) } }
Comment faire un protocole avec associatedtype "type" (type erase) ?
Pour cela, on utilise le pattern de type erase — on crée un wrapper qui stocke l'implémentation interne via des closures ou un box.
struct AnyContainer<T>: MyContainer { private let _append: (T) -> Void private let _count: () -> Int private let _subscript: (Int) -> T ... // initialisation via closures }
Le développeur a créé de nombreux protocoles avec associatedtype pour des modèles de données, puis a tenté de faire un tableau let boxes: [MyContainer], et le compilateur a renvoyé une erreur. Au final, il a fallu introduire des wrappers supplémentaires et perdre la sécurité des types.
Avantages:
Inconvénients:
Dans le projet, le protocole avec associatedtype a été utilisé pour implémenter des collections, et pour abstraction de leur utilisation, un wrapper de type erase séparé AnyCollection a été créé. Cela a permis de s'abstraire de l'implémentation spécifique des collections dans les couches ViewModel, sans perdre la sécurité de type au niveau de la couche de logique métier.
Avantages:
Inconvénients: