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.
- Si un protocole spécifie un initialiseur comme méthode requise, cet initialiseur doit être marqué à l'aide du
required
mot - clé de Swift .
- Swift a un ensemble spécial de règles d'héritage concernant les
init
méthodes.
Le tl; dr est le suivant:
Si vous implémentez des initialiseurs, vous n'héritez plus d'aucun des initialiseurs désignés de la superclasse.
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:
Contrairement aux sous-classes d'Objective-C, les sous-classes Swift n'héritent pas par défaut de leurs initialiseurs de superclasse. L'approche de Swift empêche une situation dans laquelle un simple initialiseur d'une superclasse est hérité par une sous-classe plus spécialisée et est utilisé pour créer une nouvelle instance de la sous-classe qui n'est pas complètement ou correctement initialisée.
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 1
Si votre sous-classe ne définit aucun initialiseur désigné, elle hérite automatiquement de tous ses initialiseurs désignés par 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 - alors elle hérite automatiquement de tous les initialiseurs de commodité de la superclasse.
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 place InfoBar
, 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 celles init(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:
class Foo {
var foo: String
init(foo: String) {
self.foo = foo
}
}
class Bar: Foo {
var bar: String
init(foo: String, bar: String) {
self.bar = bar
super.init(foo: foo)
}
}
let x = Bar(foo: "Foo")
Ce qui présente l'erreur suivante:
Argument manquant pour le paramètre «bar» dans l'appel.
Si c'était Objective-C, il n'aurait aucun problème à hériter. Si nous initialisions a Bar
avec initWithFoo:
en Objective-C, la self.bar
propriété serait simplement nil
. 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. Ce self.bar
n'est pas facultatif et ne peut pas l'être nil
.
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
des init(foo: String, bar: String)
, en tant que tels:
class Bar: Foo {
var bar: String
}
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 :
Problème: la classe 'Bar' n'a pas d'initialiseur
Fix-It: La propriété stockée 'bar' sans initialiseurs empêche les initialiseurs synthétisés
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 ça required
?
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 des init
méthodes spéciales (telles que NSCoding
) requiert que la classe marque ces init
méthodes comme required
.
Prenons cet exemple:
protocol InitProtocol {
init(foo: Int)
}
class ConformingClass: InitProtocol {
var foo: Int
init(foo: Int) {
self.foo = foo
}
}
Cela ne compile pas. Il génère l'avertissement suivant:
Problème: l' exigence d'initialisation 'init (foo :)' ne peut être satisfaite que par un initialiseur 'requis' dans la classe non finale 'ConformingClass'
Fix-It: Insertion requise
Il veut que je fasse l' init(foo: Int)
initialiseur requis. Je pourrais aussi le rendre heureux en créant la classe final
(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 au InitProtocol
. 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émenter init(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 en required
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-C instanceType
). Mais pour ce faire, je dois en fait utiliser une required
méthode d'initialisation.
class Box {
var size: CGSize
init(size: CGSize) {
self.size = size
}
class func factory() -> Self {
return self.init(size: CGSizeZero)
}
}
Cela génère l'erreur:
La construction d'un objet de type de classe 'Self' avec une valeur de métatype doit utiliser un initialiseur 'obligatoire'
C'est fondamentalement le même problème. Si nous sous Box
- classes , nos sous-classes hériteront de la méthode de classe factory
. Nous pourrions donc appeler SubclassedBox.factory()
. Cependant, sans le required
mot clé sur la init(size:)
méthode, Box
les sous-classes de s ne sont pas garanties d'hériter de celle self.init(size:)
qui factory
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 une required
méthode d'initialisation et nous rencontrerons exactement les mêmes problèmes que vous avez rencontrés ici avec le NSCoding
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 la required
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:
class Foo {
init(a: Int, b: Int, c: Int) {
// do nothing
}
}
class Bar: Foo {
init(string: String) {
super.init(a: 0, b: 1, c: 2)
// do more nothing
}
}
let f = Foo(a: 0, b: 1, c: 2)
let b = Bar(a: 0, b: 1, c: 2)
Cela échoue à compiler.
Le message d'erreur qu'il donne est un peu trompeur:
Argument supplémentaire 'b' dans l'appel
Mais le point est, Bar
ne hérite pas de Foo
de » init
méthodes parce qu'elle n'a pas satisfait à l'une des deux cas particuliers pour hériter des init
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.
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.