J'ai donc mis à jour vers Xcode 6 beta 5 aujourd'hui et j'ai remarqué que j'avais reçu des erreurs dans presque toutes mes sous-classes de classes Apple.
L'erreur déclare:
La classe 'x' n'implémente pas les membres requis de sa superclasse
Voici un exemple que j'ai choisi car cette classe est actuellement assez légère, donc elle sera facile à publier.
class InfoBar: SKSpriteNode { //Error message here
let team: Team
let healthBar: SKSpriteNode
init(team: Team, size: CGSize) {
self.team = team
if self.team == Team.TeamGood {
healthBar = SKSpriteNode(color: UIColor.greenColor(), size:size)
}
else {
healthBar = SKSpriteNode(color: UIColor.redColor(), size:size)
}
super.init(texture:nil, color: UIColor.darkGrayColor(), size: size)
self.addChild(healthBar)
}
}
Ma question est donc la suivante: pourquoi est-ce que je reçois cette erreur et comment puis-je la corriger? Qu'est-ce que je ne mets pas en œuvre? J'appelle un initialiseur désigné.
la source
init(collection:MPMediaItemCollection)
. Vous devez fournir une véritable collection d'articles multimédias; c'est le but de cette classe. Cette classe ne peut tout simplement pas être instanciée sans un. Il va analyser la collection et initialiser une douzaine de variables d'instance. C'est tout l'intérêt de ceci étant le seul et unique initialiseur désigné! Ainsi,init(coder:)
n'a aucun MPMediaItemCollection significatif (ou même dénué de sens) à fournir ici; seule l'fatalError
approche est la bonne.init(collection:MPMediaItemCollection!)
. Cela permettraitinit(coder:)
de passer nul. Mais alors j'ai réalisé: non, maintenant vous trompez simplement le compilateur. Passer nul n'est pas acceptable, alors lancez lefatalError
et continuez. :)Il y a deux éléments absolument cruciaux d'informations spécifiques à Swift qui manquent dans les réponses existantes et qui, je pense, aident à éclaircir complètement cela.
required
mot - clé de Swift .init
méthodes.Le tl; dr est le suivant:
Les seuls initialiseurs, le cas échéant, dont vous hériterez, sont des initialiseurs pratiques de super classe qui pointent vers un initialiseur désigné que vous avez remplacé.
Alors ... prêt pour la version longue?
Swift a un ensemble spécial de règles d'héritage concernant les
init
méthodes.Je sais que c'était le deuxième de deux points que j'ai soulevés, mais nous ne pouvons pas comprendre le premier point, ni pourquoi le
required
mot-clé existe même jusqu'à ce que nous comprenions ce point. Une fois que nous comprenons ce point, l'autre devient assez évident.Toutes les informations que je couvre dans cette section de cette réponse proviennent de la documentation d'Apple disponible ici .
À partir de la documentation Apple:
Soulignez le mien.
Donc, directement à partir de la documentation Apple, nous voyons que les sous-classes Swift n'hériteront pas toujours (et généralement pas) des
init
méthodes de leur superclasse .Alors, quand héritent-ils de leur superclasse?
Il existe deux règles qui définissent le moment où une sous-classe hérite des
init
méthodes de son parent. À partir de la documentation Apple:Règle 2 ne sont pas particulièrement à cette conversation , car
SKSpriteNode
l »init(coder: NSCoder)
est peu susceptible d'être une méthode pratique.Ainsi, votre
InfoBar
classe héritait de l'required
initialiseur jusqu'au point que vous avez ajoutéinit(team: Team, size: CGSize)
.Si vous n'aviez pas fourni cette
init
méthode et à la placeInfoBar
, vous aviez rendu les propriétés ajoutées facultatives ou leur fournir des valeurs par défaut, alors vous auriez toujours héritéSKSpriteNode
de cellesinit(coder: NSCoder)
. Cependant, lorsque nous avons ajouté notre propre initialiseur personnalisé, nous avons cessé d'hériter des initialiseurs désignés de notre superclasse (et des initialiseurs pratiques qui ne pointaient pas vers les initialiseurs que nous avons implémentés).Donc, à titre d'exemple simpliste, je présente ceci:
Ce qui présente l'erreur suivante:
Si c'était Objective-C, il n'aurait aucun problème à hériter. Si nous initialisions a
Bar
avecinitWithFoo:
en Objective-C, laself.bar
propriété serait simplementnil
. Ce n'est probablement pas génial, mais c'est un état parfaitement valide pour l'objet. Ce n'est pas un état parfaitement valide pour l'objet Swift. Ceself.bar
n'est pas facultatif et ne peut pas l'êtrenil
.Encore une fois, la seule façon d'hériter des initialiseurs est de ne pas fournir les nôtres. Donc, si nous essayons d'hériter en supprimant
Bar
desinit(foo: String, bar: String)
, en tant que tels:Nous revenons maintenant à l'héritage (en quelque sorte), mais cela ne compilera pas ... et le message d'erreur explique exactement pourquoi nous n'héritons pas des
init
méthodes de superclasse :Si nous avons ajouté des propriétés stockées dans notre sous-classe, il n'y a pas de moyen Swift possible de créer une instance valide de notre sous-classe avec les initialiseurs de superclasse qui ne pourraient pas connaître les propriétés stockées de notre sous-classe.
Ok, eh bien, pourquoi dois-je mettre
init(coder: NSCoder)
en œuvre du tout? Pourquoi çarequired
?Les
init
méthodes de Swift peuvent jouer avec un ensemble spécial de règles d'héritage, mais la conformité du protocole est toujours héritée le long de la chaîne. Si une classe parente est conforme à un protocole, ses sous-classes doivent se conformer à ce protocole.Normalement, ce n'est pas un problème, car la plupart des protocoles ne nécessitent que des méthodes qui ne fonctionnent pas avec des règles d'héritage spéciales dans Swift, donc si vous héritez d'une classe conforme à un protocole, vous héritez également de tous les méthodes ou propriétés qui permettent à la classe de satisfaire la conformité du protocole.
Cependant, rappelez-vous que les
init
méthodes de Swift fonctionnent selon un ensemble spécial de règles et ne sont pas toujours héritées. Pour cette raison, une classe conforme à un protocole qui requiert desinit
méthodes spéciales (telles queNSCoding
) requiert que la classe marque cesinit
méthodes commerequired
.Prenons cet exemple:
Cela ne compile pas. Il génère l'avertissement suivant:
Il veut que je fasse l'
init(foo: Int)
initialiseur requis. Je pourrais aussi le rendre heureux en créant la classefinal
(ce qui signifie que la classe ne peut pas être héritée).Alors, que se passe-t-il si je sous-classe? À partir de là, si je sous-classe, je vais bien. Cependant, si j'ajoute des initialiseurs, je n'hérite plus
init(foo:)
. Ceci est problématique car maintenant je ne suis plus conforme auInitProtocol
. Je ne peux pas faire de sous-classe à partir d'une classe conforme à un protocole, puis décider soudainement que je ne veux plus me conformer à ce protocole. J'ai hérité de la conformité du protocole, mais à cause de la façon dont Swift fonctionne avec l'init
héritage de méthode, je n'ai pas hérité d'une partie de ce qui est nécessaire pour se conformer à ce protocole et je dois l'implémenter.D'accord, tout cela a du sens. Mais pourquoi ne puis-je pas recevoir un message d'erreur plus utile?
On peut soutenir que le message d'erreur pourrait être plus clair ou meilleur s'il spécifiait que votre classe n'était plus conforme au
NSCoding
protocole hérité et que pour le corriger, vous devez l'implémenterinit(coder: NSCoder)
. Sûr.Mais Xcode ne peut tout simplement pas générer ce message car ce ne sera pas toujours le problème réel de ne pas implémenter ou d'hériter d'une méthode requise. Il y a au moins une autre raison de créer des
init
méthodes enrequired
plus de la conformité du protocole, et ce sont les méthodes d'usine.Si je veux écrire une méthode d'usine appropriée, je dois spécifier le type de retour à être
Self
(l'équivalent de Swift d'Objective-CinstanceType
). Mais pour ce faire, je dois en fait utiliser unerequired
méthode d'initialisation.Cela génère l'erreur:
C'est fondamentalement le même problème. Si nous sous
Box
- classes , nos sous-classes hériteront de la méthode de classefactory
. Nous pourrions donc appelerSubclassedBox.factory()
. Cependant, sans lerequired
mot clé sur lainit(size:)
méthode,Box
les sous-classes de s ne sont pas garanties d'hériter de celleself.init(size:)
quifactory
appelle.Nous devons donc créer cette méthode
required
si nous voulons une méthode d'usine comme celle-ci, et cela signifie que si notre classe implémente une méthode comme celle-ci, nous aurons unerequired
méthode d'initialisation et nous rencontrerons exactement les mêmes problèmes que vous avez rencontrés ici avec leNSCoding
protocole.En fin de compte, tout se résume à la compréhension de base que les initialiseurs de Swift jouent par un ensemble légèrement différent de règles d'héritage, ce qui signifie que vous n'êtes pas assuré d'hériter des initialiseurs de votre superclasse. Cela se produit car les initialiseurs de superclasse ne peuvent pas connaître vos nouvelles propriétés stockées et ne peuvent pas instancier votre objet dans un état valide. Mais, pour diverses raisons, une superclasse peut marquer un initialiseur comme
required
. Quand c'est le cas, nous pouvons soit utiliser l'un des scénarios très spécifiques par lesquels nous héritons réellement de larequired
méthode, soit nous devons l'implémenter nous-mêmes.Le point principal ici est que si nous obtenons l'erreur que vous voyez ici, cela signifie que votre classe n'implémente pas du tout la méthode.
Comme peut-être un dernier exemple pour explorer le fait que les sous-classes Swift n'héritent pas toujours des
init
méthodes de leurs parents (ce qui, je pense, est absolument essentiel pour comprendre pleinement ce problème), considérons cet exemple:Cela échoue à compiler.
Le message d'erreur qu'il donne est un peu trompeur:
Mais le point est,
Bar
ne hérite pas deFoo
de »init
méthodes parce qu'elle n'a pas satisfait à l'une des deux cas particuliers pour hériter desinit
méthodes de la classe parente.Si c'était Objective-C, nous en hériterions
init
sans problème, car Objective-C est parfaitement heureux de ne pas initialiser les propriétés des objets (bien qu'en tant que développeur, vous n'auriez pas dû être satisfait de cela). Dans Swift, cela ne fonctionnera tout simplement pas. Vous ne pouvez pas avoir un état non valide et l'héritage d'initialiseurs de superclasse ne peut conduire qu'à des états d'objet non valides.la source
Pourquoi ce problème s'est-il posé? Eh bien, le fait est qu'il a toujours été important (c'est-à-dire en Objective-C, depuis le jour où j'ai commencé à programmer Cocoa sous Mac OS X 10.0) de gérer les initialiseurs que votre classe n'est pas prête à gérer. Les documents ont toujours été très clairs sur vos responsabilités à cet égard. Mais combien d'entre nous ont pris la peine de les remplir, complètement et à la lettre? Probablement aucun de nous! Et le compilateur ne les a pas appliqués; tout était purement conventionnel.
Par exemple, dans ma sous-classe de contrôleur de vue Objective-C avec cet initialiseur désigné:
... il est crucial de nous transmettre une véritable collection d'éléments multimédias: l'instance ne peut tout simplement pas exister sans une. Mais je n'ai pas écrit de «stopper» pour empêcher quelqu'un de m'initialiser à la
init
place. Je devrais avoir écrit un ( en fait, à proprement parler, je l' ai écrit une mise en oeuvreinitWithNibName:bundle:
, l'initialiseur désigné hérité); mais j'étais trop paresseux pour me déranger, parce que je "savais" que je n'initialiserais jamais incorrectement ma propre classe de cette façon. Cela a laissé un trou béant. En Objective-C, quelqu'un peut appeler bare-bonesinit
, laissant mes ivars non initialisés, et nous remontons le ruisseau sans pagaie.Swift, merveilleusement, me sauve de moi-même dans la plupart des cas. Dès que j'ai traduit cette application en Swift, tout le problème a disparu. Swift crée efficacement un bouchon pour moi! Si
init(collection:MPMediaItemCollection)
est le seul initialiseur désigné déclaré dans ma classe, je ne peux pas être initialisé en appelant bare-bonesinit()
. C'est un miracle!Ce qui s'est passé dans la graine 5, c'est simplement que le compilateur a réalisé que le miracle ne fonctionne pas dans le cas de
init(coder:)
, car en théorie, une instance de cette classe pourrait provenir d'un nib, et le compilateur ne peut pas l'empêcher - et quand le nib charges,init(coder:)
sera appelée. Ainsi, le compilateur vous oblige à écrire explicitement le stopper. Et tout à fait raison aussi.la source
fatalError
bouchon décrit dans stackoverflow.com/a/25128815/341994 . Faites-en simplement un extrait de code utilisateur et à partir de maintenant, vous pouvez simplement le placer là où vous en avez besoin. Prend une demi-seconde.ajouter
la source