Istniejące odpowiedzi mówią tylko połowę convenience
historii. Druga połowa historii, połowa, której żadna z istniejących odpowiedzi nie obejmuje, odpowiada na pytanie, które Desmond zamieścił w komentarzach:
Dlaczego Swift miałby zmuszać mnie do umieszczenia convenience
przed moim inicjatorem tylko dlatego, że muszę self.init
z niego zadzwonić ? `
Poruszyłem go nieco w tej odpowiedzi , w której szczegółowo omawiam kilka reguł inicjowania języka Swift, ale główny nacisk położono na required
słowo. Ale ta odpowiedź wciąż odnosiła się do czegoś, co jest istotne dla tego pytania i tej odpowiedzi. Musimy zrozumieć, jak działa dziedziczenie inicjalizatora Swift.
Ponieważ Swift nie zezwala na niezainicjowane zmienne, nie ma gwarancji, że odziedziczysz wszystkie (lub jakiekolwiek) inicjatory z klasy, z której dziedziczysz. Jeśli utworzymy podklasę i dodamy jakiekolwiek niezainicjowane zmienne instancji do naszej podklasy, przestaliśmy dziedziczyć inicjatory. Dopóki nie dodamy własnych inicjatorów, kompilator będzie na nas krzyczał.
Dla jasności, niezainicjowana zmienna instancji to dowolna zmienna instancji, której nie nadano wartości domyślnej (należy pamiętać, że opcje i niejawnie rozpakowane opcje opcjonalne automatycznie przyjmują wartość domyślną nil
).
Więc w tym przypadku:
class Foo {
var a: Int
}
a
jest niezainicjowaną zmienną instancji. To się nie skompiluje, chyba że podamy a
wartość domyślną:
class Foo {
var a: Int = 0
}
lub zainicjuj a
w metodzie inicjalizującej:
class Foo {
var a: Int
init(a: Int) {
self.a = a
}
}
Zobaczmy teraz, co się stanie, jeśli podklasujemy Foo
, dobrze?
class Bar: Foo {
var b: Int
init(a: Int, b: Int) {
self.b = b
super.init(a: a)
}
}
Dobrze? Dodaliśmy zmienną i dodaliśmy inicjalizator, aby ustawić wartość, aby b
mogła się skompilować. W zależności od tego, co język idziesz z, można spodziewać się, że Bar
odziedziczył Foo
„s inicjator, init(a: Int)
. Ale tak nie jest. A jak to możliwe? Jak to Foo
jest init(a: Int)
wiedza, jak przypisać wartość do b
zmiennej, która Bar
dodana? Tak nie jest. Dlatego nie możemy zainicjować Bar
instancji za pomocą inicjatora, który nie może zainicjować wszystkich naszych wartości.
Co to wszystko ma wspólnego convenience
?
Przyjrzyjmy się regułom dziedziczenia inicjatorów :
Zasada nr 1
Jeśli twoja podklasa nie definiuje żadnych wyznaczonych inicjatorów, automatycznie dziedziczy wszystkie wyznaczone inicjatory nadklasy.
Zasada 2
Jeśli Twoja podklasa zapewnia implementację wszystkich wyznaczonych przez nią inicjatorów nadklasy - albo przez dziedziczenie ich zgodnie z regułą 1, albo przez dostarczenie niestandardowej implementacji jako części swojej definicji - wówczas automatycznie dziedziczy wszystkie inicjatory wygody nadklasy.
Zwróć uwagę na regułę 2, która wspomina o wygodnych inicjatorach.
Więc co convenience
Hasło nie zrobić, to wskazać nam inicjalizatory które mogą być dziedziczone przez podklasy, że zmienne instancji dodatek bez wartości domyślnych.
Weźmy przykładową Base
klasę:
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)
}
}
Zauważ, że mamy convenience
tutaj trzy inicjatory. Oznacza to, że mamy trzy inicjatory, które mogą być dziedziczone. Mamy jeden wyznaczony inicjator (wyznaczony inicjator to po prostu dowolny inicjalizator, który nie jest wygodnym inicjatorem).
Instancje klasy bazowej możemy utworzyć na cztery różne sposoby:
Stwórzmy więc podklasę.
class NonInheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
}
Dziedziczymy z Base
. Dodaliśmy własną zmienną instancji i nie nadaliśmy jej wartości domyślnej, więc musimy dodać własne inicjatory. Dodaliśmy jeden, init(a: Int, b: Int, c: Int)
ale nie pasuje podpis Base
klasy jest wyznaczony Inicjator: init(a: Int, b: Int)
. Oznacza to, że nie dziedziczymy żadnych inicjatorów z Base
:
Więc co by się stało, gdybyśmy odziedziczyli po Base
, ale poszliśmy dalej i zaimplementowaliśmy inicjator, który pasuje do wyznaczonego inicjatora z 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)
}
}
Teraz, oprócz dwóch inicjatorów, które zaimplementowaliśmy bezpośrednio w tej klasie, ponieważ zaimplementowaliśmy inicjator pasujący Base
do wyznaczonego inicjatora klasy, możemy dziedziczyć wszystkie inicjatory Base
klasy convenience
:
Fakt, że inicjator z pasującym podpisem jest oznaczony jako, convenience
nie ma tutaj znaczenia. Oznacza to tylko, że Inheritor
ma tylko jeden wyznaczony inicjator. Więc gdybyśmy dziedziczyli z Inheritor
, musielibyśmy po prostu zaimplementować ten jeden wyznaczony inicjator, a następnie odziedziczylibyśmy Inheritor
wygodny inicjator, co z kolei oznacza, że zaimplementowaliśmy wszystkie Base
wyznaczone inicjatory i możemy dziedziczyć jego convenience
inicjatory.