ProgrammationDéveloppeur iOS

Qu'est-ce que les types associés de protocoles en Swift et quelle est leur différence par rapport aux paramètres génériques ? Quand et pourquoi utiliser associatedtype dans les protocoles ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question

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.

Problème

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.

Solution

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 :

  • Permet de créer des protocoles avec paramétrage par type (plus proche du concept de templates en C++),
  • Impossible d'utiliser un protocole avec associatedtype "comme type" directement (let x: MyContainer // erreur),
  • Adapté pour construire des collections généralisées complexes, des structures algébriques et des API.

Questions pièges.

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 }

Erreurs typiques et anti-patterns

  • Essayer d'utiliser un protocole avec associatedtype comme type directement,
  • Spécifier associatedtype sans nécessité — lorsqu'un paramètre générique serait plus simple,
  • Créer des types associés avec des noms identiques dans différents protocoles sans liaison explicite à un seul type.

Exemple de la vie réelle

Cas négatif

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:

  • Flexibilité lors de la conception

Inconvénients:

  • Problèmes sérieux de conversion à un "type"
  • Complexité dans le support et le refactoring

Cas positif

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:

  • Typage clair pour la logique métier
  • Possibilité de remplacer les implémentations des collections

Inconvénients:

  • Une surcharge apparaît avec le type erase (un peu de code compliqué)