ProgrammationDéveloppeur iOS/Swift

Qu'est-ce que la délégation d'initialisation (Initializer Delegation) en Swift, comment fonctionnent les règles de délégation entre les initialisateurs désignés et les initialisateurs de commodité, et comment cela affecte-t-il l'héritage ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

La délégation d'initialisation est un système de délégation d'initialisation intégré dans Swift pour les classes. En Swift, il existe historiquement une distinction entre l'initialisateur désigné (l'initialisateur principal de la classe, responsable de l'initialisation complète de toutes les propriétés) et l'initialisateur de commodité (un initialisateur auxiliaire qui facilite la création d'instances avec différents ensembles de paramètres).

Problème : si les initialisateurs sont exécutés de manière chaotique entre les classes et leurs héritiers, il est possible que la classe de base ne soit pas entièrement initialisée, ou que l'initialisation se produise plus d'une fois, ce qui entraînera un objet invalide.

Solution — règle stricte :

  1. L'initialisateur désigné appelle toujours l'initialisateur désigné de la superclasse.
  2. L'initialisateur de commodité appelle toujours un autre initialisateur de la même classe (soit désigné, soit un autre de commodité).
  3. L'initialisateur de commodité ne peut pas initialiser directement la superclasse.

Exemple de code :

class Vehicle { var wheels: Int // Initialisateur désigné init(wheels: Int) { self.wheels = wheels } // Initialisateur de commodité convenience init() { self.init(wheels: 4) } } class Car: Vehicle { var color: String // Initialisateur désigné init(wheels: Int, color: String) { self.color = color super.init(wheels: wheels) } // Initialisateur de commodité convenience init(color: String) { self.init(wheels: 4, color: color) } }

Caractéristiques clés :

  • Duplications de code minimales lors de la création d'instances avec différents ensembles de paramètres.
  • Support de l'héritage sécurisé des classes.
  • Initialisation correcte garantie de toutes les propriétés de l'objet.

Questions piégées.

Question 1 : Un initialisateur de commodité peut-il appeler directement l'initialisateur de la superclasse via super.init ?

Non, l'initialisateur de commodité délègue toujours l'initialisation à un autre initialisateur de la même classe, qui peut ensuite appeler l'initialisateur désigné de la superclasse.

Question 2 : Que se passe-t-il si l'initialisateur requis n'est pas implémenté dans la sous-classe ?

Si la superclasse a un initialisateur requis, il doit obligatoirement être redéfini dans chaque sous-classe (en utilisant required), sinon le compilateur renverra une erreur.

Question 3 : Quelle est la différence entre convenience init et convenience required init ?

convenience required init est nécessaire si un initialisateur de commodité spécifique est déclaré comme requis dans la superclasse pour assurer le support de l'initialisation dans les hiérarchies d'héritage.

Erreurs communes et anti-patterns

  • Chaîne d'appels incorrecte de convenience/init et violation de l'initialisation des propriétés.
  • Oublient d'implémenter l'initialisateur requis dans la sous-classe.
  • Appellent super.init depuis convenience, ce qui est invalide.

Exemple de la vie réelle

Cas négatif

Un développeur a appelé super.init depuis l'initialisateur de commodité. Le code a été compilé uniquement grâce à l'absence de certaines restrictions, mais au moment de l'exécution, une erreur s'est produite : toutes les propriétés de l'objet n'étaient pas initialisées.

Avantages :

  • Le code semblait compréhensible pour les débutants, car il rappelait des implémentations similaires dans d'autres langages.

Inconvénients :

  • Des bogues sont apparus lors de l'héritage — les classes enfants n'étaient pas initialisées correctement, ce qui a fait planter l'application.
  • Un refactoring était nécessaire.

Cas positif

Des initialisateurs désignés et de commodité clairement structurés ont été utilisés avec des appels explicites l'un à l'autre. La logique était appelée strictement selon les règles de Swift, garantissant ainsi une initialisation toujours transparente et correcte.

Avantages :

  • Facilité d'extension et de maintenance de la hiérarchie des classes.
  • Code de duplication éliminé.
  • Test simplifié.

Inconvénients :

  • Nécessité de documenter attentivement l'héritage des initialisateurs dans de grandes hiérarchies.