Qu'est-ce que le codeur init aDecoder exactement?

122

J'apprends le développement iOS à partir d'un cours en ligne et chaque fois que je crée une vue personnalisée (cellule de vue de tableau personnalisée, cellule de vue de collection, etc.), l'instructeur implémente toujours cet initialiseur:

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

Pourquoi exactement dois-je toujours appeler ça? Qu'est ce que ça fait? Puis-je mettre des propriétés dans l'init?

JasonP
la source
5
Cette réponse vous aidera à stackoverflow.com/questions/24036393/… Merci
Seungyoun Yi
2
Si vous sous-classez un objet qui implémente, NSCodingvous devez implémenter cet initialiseur, car il est requis pour les classes qui implémentent NSCoding. Vous devez au moins appeler la méthode d'initialisation de la superclasse. Si le NSCodercontient des propriétés encodées pour votre classe, vous pouvez utiliser cette méthode pour les récupérer
Paulw11
1
Aussi, je vous recommande de lire la section sur l'initialisation d'objets dans le livre officiel Swift d'Apple.
Nicolas Miari

Réponses:

121

Je vais commencer cette réponse dans la direction opposée: que faire si vous souhaitez enregistrer l'état de votre vue sur le disque? C'est ce qu'on appelle la sérialisation . L'inverse est la désérialisation - la restauration de l'état de l'objet à partir du disque.

Le NSCodingprotocole définit deux méthodes pour sérialiser et désérialiser des objets:

encodeWithCoder(_ aCoder: NSCoder) {
    // Serialize your object here
}

init(coder aDecoder: NSCoder) {
    // Deserialize your object here
}

Alors pourquoi est-il nécessaire dans votre classe personnalisée? La réponse est Interface Builder. Lorsque vous faites glisser un objet sur un storyboard et le configurez, Interface Builder sérialise l'état de cet objet sur le disque, puis le désérialise lorsque le storyboard apparaît à l'écran. Vous devez indiquer à Interface Builder comment procéder. À tout le moins, si vous n'ajoutez aucune nouvelle propriété à votre sous-classe, vous pouvez simplement demander à la superclasse de faire l'emballage et le déballage pour vous, d'où l' super.init(coder: aDecoder)appel. Si votre sous-classe est plus complexe, vous devez ajouter votre propre code de sérialisation et de désérialisation pour la sous-classe.

Cela contraste avec l'approche de Visual Studio, qui consiste à écrire du code dans un fichier caché pour créer l'objet au moment de l'exécution.

Code différent
la source
Pourquoi ne pas tout mettre dans awakeFromNib et oublier de l'utiliser init(coder aCoder : NSCoder)?
Honey
@Honey - en un mot, "parfois vous ne pouvez pas faire ça". Vous pouvez généralement, mais pas toujours.
Fattie
@Fattie est-ce que les détails de ne pas le faire sont trop complexes ou inutiles à savoir? Sinon, cela vous dérange-t-il d'expliquer?
Honey
9
@Honey si vous souhaitez configurer votre objet dans Interface Builder awakeFromNibne fonctionnera pas. awakeFromNibest appelée au moment de l'exécution . Tout ce que vous faites dans Interface Builder est au moment de la conception . Pour transporter ce que vous avez fait au moment de la conception au temps d' exécution, il faut encodeWithCoder(enregistrer) et init(coder:)(charger)
Code différent
3
@Honey si vous n'utilisez pas Interface Builder pour configurer votre classe personnalisée (c'est-à-dire le faire par programme avec du code), vous pouvez le faire dans awakeFromNibouinitWIthFrame
Code différent
28

La nécessité d'implémenter cet initialiseur est une conséquence de deux choses:

  1. Le principe de substitution de Liskov . Si S est une sous-classe de T (par exemple MyViewControllerest une sous-classe de ViewController), alors les objets S (instances de MyViewController) doivent pouvoir être substitués là où T objets (instances de ViewController) sont attendus.

  2. Les initialiseurs ne sont pas hérités dans Swift si des initialiseurs sont explicitement définis dans la sous-classe. Si un initialiseur est explicitement fourni, alors tous les autres doivent être explicitement fournis (qui peuvent alors simplement appeler super.init(...)). Voir cette question pour la justification. C'est en Java, mais s'applique toujours.

Au point 1, tout ce que l'original ViewControllerpeut faire, la MyViewControllersous - classe devrait pouvoir le faire. Une telle chose est de pouvoir être initialisé à partir d'une donnée NSCoder. Au point 2, votre MyViewControllersous-classe n'hérite pas automatiquement de cette capacité. Ainsi, vous devez fournir manuellement l'initialiseur qui remplit cette condition. Dans ce cas, il vous suffit de déléguer jusqu'à la superclasse, pour qu'elle fasse ce qu'elle ferait habituellement.

Alexander - Réintégrer Monica
la source
1
Il est parfaitement logique que les constructeurs ne soient pas hérités: si vous initialisez une instance de la classe dérivée à l'aide de l'initialiseur (hérité) de la classe de base, les propriétés non héritées nouvellement définies ("ajoutées") par la classe dérivée ne seront jamais être initialisé.
Nicolas Miari
3
En fait, les initialiseurs sont hérités dans Swift, étant donné que vous ne fournissez aucune de vos propres implémentations d'initialisation dans votre sous-classe. Si vos propriétés non héritées nouvellement définies ont des valeurs par défaut, vous pouvez vous en sortir en n'écrivant aucun initialiseur dans votre sous-classe et en héritant simplement de tous les initialiseurs de votre superclasse. Voir ici
TheBaj