Les réponses existantes ne racontent que la moitié de l' convenience
histoire. L'autre moitié de l'histoire, la moitié qu'aucune des réponses existantes ne couvre, répond à la question que Desmond a postée dans les commentaires:
Pourquoi Swift me forcerait-il à mettre convenience
devant mon initialiseur simplement parce que j'ai besoin d'appeler self.init
depuis celui-ci?
Je l'ai légèrement abordé dans cette réponse , dans laquelle je couvre en détail plusieurs règles d'initialisation de Swift, mais l'accent était principalement mis sur le required
mot. Mais cette réponse concernait toujours quelque chose qui est pertinent pour cette question et cette réponse. Nous devons comprendre comment fonctionne l'héritage de l'initialisation Swift.
Étant donné que Swift n'autorise pas les variables non initialisées, vous n'êtes pas assuré d'hériter de tous les initialiseurs (ou de certains) de la classe dont vous héritez. Si nous sous-classons et ajoutons des variables d'instance non initialisées à notre sous-classe, nous avons arrêté d'hériter des initialiseurs. Et jusqu'à ce que nous ajoutions nos propres initialiseurs, le compilateur nous hurlera dessus.
Pour être clair, une variable d'instance non initialisée est toute variable d'instance qui n'a pas de valeur par défaut (en gardant à l'esprit que les options et les options implicitement déballées prennent automatiquement la valeur par défaut de nil
).
Donc dans ce cas:
class Foo {
var a: Int
}
a
est une variable d'instance non initialisée. Cela ne se compilera que si nous donnons a
une valeur par défaut:
class Foo {
var a: Int = 0
}
ou initialisez a
dans une méthode d'initialisation:
class Foo {
var a: Int
init(a: Int) {
self.a = a
}
}
Maintenant, voyons ce qui se passe si nous sous Foo
- classons , d'accord?
class Bar: Foo {
var b: Int
init(a: Int, b: Int) {
self.b = b
super.init(a: a)
}
}
Droite? Nous avons ajouté une variable, et nous avons ajouté un initialiseur pour définir une valeur b
afin qu'il compile. En fonction de la langue que vous venez, vous pourriez attendre à ce que Bar
a hérité Foo
de initialiseur de », init(a: Int)
. Mais ce n'est pas le cas. Et comment le pourrait-il? Comment Foo
de » init(a: Int)
le savoir comment attribuer une valeur à la b
variable Bar
ajoutée? Ce n'est pas le cas. Nous ne pouvons donc pas initialiser une Bar
instance avec un initialiseur qui ne peut pas initialiser toutes nos valeurs.
Qu'est-ce que tout cela a à voir avec convenience
?
Eh bien, regardons les règles sur l'héritage d'initialiseur :
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 superclasse.
Remarquez la règle 2, qui mentionne les initialiseurs de commodité.
Ainsi, ce que fait le convenience
mot - clé , c'est nous indiquer quels initialiseurs peuvent être hérités par des sous-classes qui ajoutent des variables d'instance sans valeurs par défaut.
Prenons cet exemple de Base
classe:
class Base {
let a: Int
let b: Int
init(a: Int, b: Int) {
self.a = a
self.b = b
}
convenience init() {
self.init(a: 0, b: 0)
}
convenience init(a: Int) {
self.init(a: a, b: 0)
}
convenience init(b: Int) {
self.init(a: 0, b: b)
}
}
Notez que nous avons trois convenience
initialiseurs ici. Cela signifie que nous avons trois initialiseurs qui peuvent être hérités. Et nous avons un initialiseur désigné (un initialiseur désigné est simplement n'importe quel initialiseur qui n'est pas un initialiseur pratique).
Nous pouvons instancier des instances de la classe de base de quatre manières différentes:
Alors, créons une sous-classe.
class NonInheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
}
Nous héritons de Base
. Nous avons ajouté notre propre variable d'instance et nous ne lui avons pas donné de valeur par défaut, nous devons donc ajouter nos propres initialiseurs. Nous avons ajouté un, init(a: Int, b: Int, c: Int)
mais il ne correspond pas à la signature de la Base
classe est désignée initialiseur: init(a: Int, b: Int)
. Cela signifie que nous n'héritons d' aucun initialiseur de Base
:
Alors, que se passerait-il si nous héritions de Base
, mais que nous allions de l'avant et implémentions un initialiseur qui correspondait à l'initialiseur désigné Base
?
class Inheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
convenience override init(a: Int, b: Int) {
self.init(a: a, b: b, c: 0)
}
}
Maintenant, en plus des deux initialiseurs que nous avons implémentés directement dans cette classe, parce que nous avons implémenté l'initialiseur Base
désigné d' une classe correspondant à un initialiseur, nous pouvons hériter de tous Base
les convenience
initialiseurs de classe :
Le fait que l'initialiseur avec la signature correspondante soit marqué comme convenience
ne fait aucune différence ici. Cela signifie seulement qu'il Inheritor
n'y a qu'un seul initialiseur désigné. Donc, si nous héritons de Inheritor
, nous aurions juste à implémenter cet initialiseur désigné, puis nous hériterions Inheritor
de l'initialiseur de commodité, ce qui signifie que nous avons implémenté tous Base
les initialiseurs désignés et que nous pouvons hériter de ses convenience
initialiseurs.