web-dev-qa-db-fra.com

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

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

Selon le Swift Book :

Règle 1

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

Règle 2

Si votre sous-classe fournit une implémentation de tous ses initialiseurs désignés de superclasse, soit en les héritant selon la règle 1, soit en fournissant une implémentation personnalisée dans le cadre de sa définition, elle hérite automatiquement de tous les initialiseurs de commodité de superclasse. "

Cependant, la classe suivante ne fonctionne pas:

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

Au lieu de cela, je reçois:

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 n'implémente aucun de mes propres 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?

23
Alex Pretzlav

Ma compréhension de l'héritage Initializer est la même que la vôtre, et je pense que nous sommes tous les deux bien alignés sur ce que dit le livre. Je ne pense pas que ce soit un problème d'interprétation ou une mauvaise compréhension des règles énoncées. Cela dit, je ne pense pas que vous faites 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 de NSObject. L'initialiseur de commodité que j'ai fourni dans l'implémentation délègue correctement à un initialiseur désigné, avant de faire la configuration pour l'intance.

SquareShape hérite de RectShape, ne fournit pas d'initialiseur désigné et, encore une fois, conformément à la règle 1, hérite de tous les initialiseurs désignés de SquareShape. Selon la règle 2, il hérite également de l'initialiseur de commodité défini dans RectShape. Enfin, l'initialiseur de confort défini dans SquareShape délègue correctement à l'initialiseur de confort hérité, qui à son tour délègue à l'initialiseur 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 convenance doit appeler un autre initialiseur de la même classe" n'est pas appliquée par le langage. Donc, peut-être que l'initialiseur pratique pour SKShapeNode n'appelle pas réellement un initialiseur désigné. Par conséquent, même si la sous-classe 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 classes que j'ai créées moi-même.

15
Cezar

Ici, nous avons deux problèmes:

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

  • SKShapeNode a une propriété path déclarée comme CGPath!. Cela signifie que nous ne voulons pas sortir de notre initialiseur sans avoir en quelque sorte initialisé le path.

La combinaison de ces deux éléments est à l'origine du problème. En résumé, SKShapeNode est mal écrit. Il a une propriété path qui doit être initialisée; 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 path- sont des initialiseurs pratiques). Voilà le bug. Autrement dit, la source du problème est que, par commodité ou non, les méthodes shapeNodeWith... Ne sont pas du tout des initialiseurs.

Vous pouvez néanmoins 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 semble 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 sommes maintenant libres de faire référence à self (nous n'obtenons plus "l'utilisation de" self "dans la délégation de l'initialiseur avant self.init est appelé "erreur) et satisfait à la deuxième exigence.

26
matt

En s'appuyant sur la réponse de Matt, nous avons dû inclure une fonction supplémentaire, sinon le compilateur s'est plaint d'invoquer un initialiseur sans argument.

Voici ce qui a fonctionné pour sous-classer 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")
    }
}
5
Crashalot

Bonne nouvelle de 2019! Je peux signaler que j'ai maintenant une sous-classe SKShape qui a les trois initialiseurs suivants:

    override init() {
    super.init()
}

convenience init(width: CGFloat, point: CGPoint) {
    self.init(circleOfRadius: width/2)
    self.fillColor = .green
    self.position = point
}

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

et cela se comporte exactement comme prévu: lorsque vous appelez l'initialiseur de confort, vous obtenez des points verts dans la position souhaitée. (Le double appel de init () tel que décrit par @matt et @Crashalot, d'autre part, entraîne maintenant une erreur).

Je préférerais avoir la possibilité de modifier SKShapeNodes dans l'éditeur de scène .sks, mais vous ne pouvez pas tout avoir. ENCORE.

1
green_knight