Les protocoles avec associatedtype et des contraintes where sont un puissant mécanisme d'abstraction de types en Swift. Cela est souvent utilisé pour réaliser des protocoles génériques, où le type concret est défini par l'implémentation. Historiquement, dans les premières versions de Swift, les protocoles avec associatedtype ne pouvaient pas être utilisés comme types concrets (existential types), ce qui a limité l'application de tels protocoles dans les collections et les interfaces. Plus tard, Swift a ajouté des mécanismes pour restreindre associatedtype à l'aide de conditions where, ce qui permet de créer des abstractions flexibles et sûres pour des scénarios complexes.
Problème : il se pose souvent la question de créer un protocole qui permet de s'abstraire des collections ou des gestionnaires de données spécifiques. Cependant, les protocoles standard peuvent ne pas être suffisamment flexibles : tous les types ne peuvent pas être généralisés sans connaître les types connexes, et la logique d'héritage des protocoles avec associatedtype n'est pas toujours évidente.
Solution : utiliser des contraintes where pour préciser les exigences des implémentations, permettant de créer des protocoles avec un comportement adaptatif.
Exemple de code :
protocol Storage { associatedtype Element func add(_ item: Element) } // Protocole restreint pour stocker uniquement des nombres protocol NumericStorage: Storage where Element: Numeric { func sum() -> Element } struct IntStorage: NumericStorage { private var items: [Int] = [] func add(_ item: Int) { items.append(item) } func sum() -> Int { items.reduce(0, +) } }
Caractéristiques clés :
Peut-on utiliser un protocole avec associatedtype comme type (par exemple, pour une collection) ?
Non, pas directement. Un protocole avec associatedtype est un PAT (protocol with associated type) et ne peut pas être utilisé comme type existential. On ne peut pas, par exemple, déclarer un tableau [Storage], cela ne peut être fait qu'en utilisant le type erasure.
Comment mettre en œuvre le type-erasure pour un protocole avec associatedtype ?
À travers un wrapper d'assistance qui cache le type d'implémentation réel.
struct AnyStorage<T>: Storage { private let _add: (T) -> Void init<S: Storage>(_ storage: S) where S.Element == T { _add = storage.add } func add(_ item: T) { _add(item) } }
Quelle est la différence entre associatedtype et un paramètre générique de protocole ?
associatedtype définit un type concret qui doit être implémenté en conséquence, tandis que le paramètre générique est spécifié explicitement dans le protocole ou la fonction elle-même, mais ne peut pas être utilisé lors de la déclaration du protocole. Un protocole ne peut pas être générique par syntaxe, seulement via associatedtype.
Un développeur a tenté d'utiliser [Storage] pour stocker toutes les collections. Le code ne compile pas, entraînant des castings implicites ou l'utilisation d'approches Any/unsafe.
Avantages :
Inconvénients :
Un développeur a conçu AnyStorage<T> pour cacher l'implémentation concrète et a ajouté des contraintes where pour garantir le bon fonctionnement uniquement avec les types nécessaires.
Avantages :
Inconvénients :