Ajout d'initialiseurs de commodité dans la sous-class Swift

En tant qu'exercice d'apprentissage, j'essaie d'implémenter une sous-class de SKShapeNode qui fournit un nouvel initialiseur de commodité qui prend un nombre et construit un ShapeNode qui est un carré de largeur et de hauteur de nombre.

Selon le livre rapide :

Règle 1

Si votre sous-class ne définit aucun initialiseur désigné, elle hérite automatiquement de tous ses initialiseurs désignés par superclass.

Règle 2

Si votre sous-class fournit une implémentation de tous ses initialiseurs désignés par la superclass, soit en les héritant conformément à la règle 1, soit en fournissant une implémentation personnalisée dans sa définition, elle hérite automatiquement de tous les initialiseurs de la superclass.

Cependant, la class suivante ne fonctionne pas:

 class MyShapeNode : SKShapeNode { convenience init(squareOfSize value: CGFloat) { self.init(rectOfSize: CGSizeMake(value, value)) } } 

Au lieu de cela, j'ai:

 Playground execution failed: error: <REPL>:34:9: error: use of 'self' in delegating initializer before self.init is called self.init(rectOfSize: CGSizeMake(value, value)) ^ <REPL>:34:14: error: use of 'self' in delegating initializer before self.init is called self.init(rectOfSize: CGSizeMake(value, value)) ^ <REPL>:35:5: error: self.init isn't called on all paths in delegating initializer } 

Ma compréhension est que MyShapeNode devrait hériter de tous les initialiseurs de commodité de SKShapeNode parce que je SKShapeNode aucun de mes initialiseurs désignés, et parce que mon initialiseur de commodité appelle init(rectOfSize) , un autre initialiseur de commodité, cela devrait fonctionner. Qu'est-ce que je fais mal?

Ma compréhension de Initializer Inheritance est la même que la vôtre, et je pense que nous sums tous deux bien alignés sur ce que le livre indique. Je ne pense pas que ce soit une question d'interprétation ou une mauvaise compréhension des règles énoncées. Cela dit, je ne pense pas que vous fassiez quelque chose de mal.

J'ai testé ce qui suit dans un Playground et cela fonctionne comme prévu:

 class RectShape: NSObject { var size = CGSize(width: 0, height: 0) convenience init(rectOfSize size: CGSize) { self.init() self.size = size } } class SquareShape: RectShape { convenience init(squareOfSize size: CGFloat) { self.init(rectOfSize: CGSize(width: size, height: size)) } } 

RectShape hérite de NSObject et ne définit aucun initialiseur désigné. Ainsi, conformément à la Règle 1, il hérite de tous les initialiseurs désignés par NSObject . L'initialiseur de commodité que j'ai fourni dans l'implémentation délègue correctement à un initialiseur désigné, avant de faire l'installation pour l'intance.

SquareShape hérite de RectShape , ne fournit pas d'initialiseur désigné et, toujours selon la règle 1, hérite de tous les initialiseurs désignés par SquareShape . Conformément à la règle 2, il hérite également de l'initialiseur de commodité défini dans RectShape . Enfin, l'initialiseur de commodité défini dans SquareShape délègue correctement à l'initialiseur de commodité hérité, qui à son tour délègue à l'initialisateur désigné hérité.

Donc, étant donné que vous ne faites rien de mal et que mon exemple fonctionne comme prévu, j'extrapole l'hypothèse suivante:

Puisque SKShapeNode est écrit en Objective-C, la règle qui stipule que "chaque initialiseur de commodité doit appeler un autre initialiseur de la même class" n'est pas appliquée par le langage. Donc, peut-être que l'initialiseur de commodité pour SKShapeNode pas réellement un initialiseur désigné. Par conséquent, même si la sous-class MyShapeNode hérite des initialiseurs de commodité comme prévu, ils ne délèguent pas correctement à l'initialiseur désigné hérité.

Mais, encore une fois, ce n'est qu'une hypothèse. Tout ce que je peux confirmer, c'est que la mécanique fonctionne comme prévu sur les deux classs que j'ai créées moi-même.

Ici, nous avons deux problèmes:

  • SKShapeNode a seulement un initialiseur désigné: init() . Cela signifie que nous ne pouvons pas sortir de notre initialiseur sans appeler init() .

  • SKShapeNode a un path propriété déclaré comme CGPath! . Cela signifie que nous ne voulons pas sortir de notre initialiseur sans initialiser le path .

La combinaison de ces deux éléments est la source du problème. En un mot, SKShapeNode est écrit de manière incorrecte. Il a un path propriété qui doit être initialisé; par conséquent, il doit avoir un initialiseur désigné qui définit le path . Mais ce n'est pas le cas (tous ses initialiseurs de path sont des initialiseurs de commodité). C'est le bug. En d'autres termes, la source du problème est que, commodité ou non, les methods shapeNodeWith... ne sont pas du tout des initialiseurs.

Vous pouvez néanless faire ce que vous voulez faire – écrire un initialiseur de commodité sans être obligé d'écrire d'autres initialiseurs – en satisfaisant les deux exigences dans cet ordre , c'est-à-dire en l'écrivant comme ceci:

 class MyShapeNode : SKShapeNode { convenience init(squareOfSize value: CGFloat) { self.init() self.init(rectOfSize: CGSizeMake(value, value)) } } 

Cela a l'air illégal, mais ce n'est pas le cas. Une fois que nous avons appelé self.init() , nous avons satisfait à la première exigence, et nous sums maintenant libres de nous référer à self (nous n'obtenons plus l'utilisation de self en délégant l'initialiseur avant que self.init ne s'appelle erreur) et satisfaire la deuxième exigence.

Sur la base de la réponse de Matt, nous devions inclure une fonction supplémentaire, sinon le compilateur se plaignait d'invoquer un initialiseur sans arguments.

Voici ce qui a fonctionné pour sous-classr SKShapeNode:

 class CircleNode : SKShapeNode { override init() { super.init() } convenience init(width: CGFloat, point: CGPoint) { self.init() self.init(circleOfRadius: width/2) // Do stuff } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }