Considérons les deux classes:
class A {
var x: Int
init(x: Int) {
self.x = x
}
convenience init() {
self.init(x: 0)
}
}
class B: A {
init() {
super.init() // Error: Must call a designated initializer of the superclass 'A'
}
}
Je ne vois pas pourquoi cela n'est pas autorisé. En fin de compte, l'initialiseur désigné de chaque classe est appelé avec toutes les valeurs dont il a besoin, alors pourquoi dois-je me répéter dans la variable B
en init
en spécifiant à nouveau une valeur par défaut pour x
, lorsque la commodité init
dans A
va de nouveau?
Voici la règle 1 des règles "Chaînage d'initialisation" spécifiées dans le Guide de programmation Swift, qui se lit comme suit:
Règle 1: Les initialiseurs désignés doivent appeler un initialiseur désigné à partir de leur superclasse immédiate.
L'accent est à moi. Les initialiseurs désignés ne peuvent pas appeler les initialiseurs de commodité.
Un diagramme accompagne les règles pour montrer quelles "instructions" d'initialisation sont autorisées:
Considérer
class A
{
var a: Int
var b: Int
init (a: Int, b: Int) {
print("Entering A.init(a,b)")
self.a = a; self.b = b
}
convenience init(a: Int) {
print("Entering A.init(a)")
self.init(a: a, b: 0)
}
convenience init() {
print("Entering A.init()")
self.init(a:0)
}
}
class B : A
{
var c: Int
override init(a: Int, b: Int)
{
print("Entering B.init(a,b)")
self.c = 0; super.init(a: a, b: b)
}
}
var b = B()
Étant donné que tous les initialiseurs désignés de la classe A sont remplacés, la classe B héritera de tous les initialiseurs de commodité de A. L'exécution de cette opération générera
Entering A.init()
Entering A.init(a:)
Entering B.init(a:,b:)
Entering A.init(a:,b:)
Maintenant, si l’initialiseur désigné B.init (a: b :) est autorisé à appeler l’initialisateur de classe de base A.init (a :), un appel récursif à B.init (a:, b: ).
C'est parce que vous pouvez vous retrouver avec une récursion infinie. Considérer:
class SuperClass {
init() {
}
convenience init(value: Int) {
// calls init() of the current class
// so init() for SubClass if the instance
// is a SubClass
self.init()
}
}
class SubClass : SuperClass {
override init() {
super.init(value: 10)
}
}
et regardez:
let a = SubClass()
qui appelle SubClass.init()
qui appelle SuperClass.init(value:)
qui appelle SubClass.init()
.
Les règles init désignées/de commodité sont conçues pour qu'une initialisation de classe soit toujours correcte.
J'ai trouvé un travail pour ça. Ce n'est pas très beau, mais cela résout le problème de ne pas connaître les valeurs d'une super-classe ou de vouloir définir des valeurs par défaut.
Tout ce que vous avez à faire est de créer une instance de la superclasse, en utilisant la commodité init
, directement dans la init
de la sous-classe. Ensuite, vous appelez la init
désignée du super en utilisant l'instance que vous venez de créer.
class A {
var x: Int
init(x: Int) {
self.x = x
}
convenience init() {
self.init(x: 0)
}
}
class B: A {
init() {
// calls A's convenience init, gets instance of A with default x value
let intermediate = A()
super.init(x: intermediate.x)
}
}
Regardez la vidéo WWDC "403 intermediate Swift" à 18h30 pour une explication détaillée des initialiseurs et de leur héritage. Si j'ai bien compris, considérez ce qui suit:
class Dragon {
var legs: Int
var isFlying: Bool
init(legs: Int, isFlying: Bool) {
self.legs = legs
self.isFlying = isFlying
}
convenience initWyvern() {
self.init(legs: 2, isFlying: true)
}
}
Mais considérons maintenant une sous-classe de Wyrm: Un Wyrm est un dragon sans jambes ni ailes. Donc, l'initialiseur pour un Wyvern (2 jambes, 2 ailes) est mauvais pour cela! Cette erreur peut être évitée si la commodité de Wyvern-Initializer ne peut tout simplement pas être appelée, mais uniquement l'initialiseur désigné complet:
class Wyrm: Dragon {
init() {
super.init(legs: 0, isFlying: false)
}
}
Pensez à extraire le code d'initialisation de votre pratique init()
dans une nouvelle fonction d'assistance foo()
, appelez foo(...)
pour effectuer l'initialisation dans votre sous-classe.