Ponieważ Swift obsługuje przeciążanie metod i inicjatorów, możesz umieścić wiele init
obok siebie i użyć tego, co uznasz za wygodne:
class Person {
var name:String
init(name: String) {
self.name = name
}
init() {
self.name = "John"
}
}
Dlaczego więc convenience
słowo kluczowe w ogóle istniało? Co sprawia, że poniższe elementy są znacznie lepsze?
class Person {
var name:String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "John")
}
}
swift
initialization
Desmond Hume
źródło
źródło
Odpowiedzi:
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: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 podamya
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ę, żeBar
odziedziczyłFoo
„s inicjator,init(a: Int)
. Ale tak nie jest. A jak to możliwe? Jak toFoo
jestinit(a: Int)
wiedza, jak przypisać wartość dob
zmiennej, któraBar
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 :
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 podpisBase
klasy jest wyznaczony Inicjator:init(a: Int, b: Int)
. Oznacza to, że nie dziedziczymy żadnych inicjatorów zBase
: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 zBase
?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 inicjatoryBase
klasyconvenience
:Fakt, że inicjator z pasującym podpisem jest oznaczony jako,
convenience
nie ma tutaj znaczenia. Oznacza to tylko, żeInheritor
ma tylko jeden wyznaczony inicjator. Więc gdybyśmy dziedziczyli zInheritor
, musielibyśmy po prostu zaimplementować ten jeden wyznaczony inicjator, a następnie odziedziczylibyśmyInheritor
wygodny inicjator, co z kolei oznacza, że zaimplementowaliśmy wszystkieBase
wyznaczone inicjatory i możemy dziedziczyć jegoconvenience
inicjatory.źródło
init(a: Int)
pozostawiłobyb
niezainicjalizowany.Głównie przejrzystość. Z twojego drugiego przykładu,
init(name: String) { self.name = name }
jest wymagane lub wyznaczone . Musi zainicjować wszystkie stałe i zmienne. Wygodne inicjatory są opcjonalne i zwykle mogą być używane w celu ułatwienia inicjalizacji. Załóżmy na przykład, że Twoja klasa Person ma opcjonalną zmienną płeć:
var gender: Gender?
gdzie Płeć jest wyliczeniem
enum Gender { case Male, Female }
możesz mieć takie wygodne inicjatory
convenience init(maleWithName: String) { self.init(name: name) gender = .Male } convenience init(femaleWithName: String) { self.init(name: name) gender = .Female }
Wygodne inicjatory muszą wywoływać wyznaczone lub wymagane inicjatory w nich. Jeśli twoja klasa jest podklasą, musi wywołać
super.init()
w ramach swojej inicjalizacji.źródło
convenience
słowa kluczowego, ale Swift nadal by się o to martwił. To nie jest taka prostota, jakiej oczekiwałem od Apple =)Cóż, pierwszą rzeczą, która przychodzi mi do głowy, jest to, że jest używany w dziedziczeniu klas do organizacji kodu i czytelności. Kontynuując
Person
naukę, pomyśl o takim scenariuszuclass Person{ var name: String init(name: String){ self.name = name } convenience init(){ self.init(name: "Unknown") } } class Employee: Person{ var salary: Double init(name:String, salary:Double){ self.salary = salary super.init(name: name) } override convenience init(name: String) { self.init(name:name, salary: 0) } } let employee1 = Employee() // {{name "Unknown"} salary 0} let john = Employee(name: "John") // {{name "John"} salary 0} let jane = Employee(name: "Jane", salary: 700) // {{name "Jane"} salary 700}
Dzięki wygodnemu inicjalizatorowi jestem w stanie stworzyć
Employee()
obiekt bez wartości, stąd słowoconvenience
źródło
convenience
usunięciu słów kluczowych, czy Swift nie uzyska wystarczającej ilości informacji, aby zachowywać się dokładnie w ten sam sposób?convenience
słowo kluczowe, nie możesz zainicjowaćEmployee
obiektu bez żadnego argumentu.Employee()
wywołujeconvenience
inicjator (inherited, due )init()
, który wywołujeself.init(name: "Unknown")
.init(name: String)
, również wygodny inicjator dlaEmployee
, wywołuje wyznaczony inicjator.Oprócz punktów, które wyjaśnili tutaj inni użytkownicy, jest moja odrobina zrozumienia.
Mocno czuję związek między wygodnym inicjatorem a rozszerzeniami. Jak dla mnie wygodne inicjatory są najbardziej przydatne, gdy chcę zmodyfikować (w większości przypadków uczynić to krótkim lub łatwym) inicjalizację istniejącej klasy.
Na przykład jakaś klasa innej firmy, której używasz, ma
init
cztery parametry, ale w twojej aplikacji ostatnie dwa mają tę samą wartość. Aby uniknąć więcej wpisywania i sprawić, by kod był czysty, możesz zdefiniować aconvenience init
z tylko dwoma parametrami i wewnątrz niego wywołaćself.init
parametry last to z wartościami domyślnymi.źródło
convenience
przed moim inicjatorem tylko dlatego, że muszęself.init
z niego zadzwonić ? Wydaje się to zbędne i trochę niewygodne.Zgodnie z dokumentacją Swift 2.1 ,
convenience
inicjatory muszą przestrzegać pewnych określonych zasad:convenience
Inicjator może wywołać tylko intializers w tej samej klasie, nie w Super klas (tylko w poprzek, a nie w górę)convenience
Inicjator musi zadzwonić wyznaczony initializer gdzieś w sieciconvenience
Inicjator nie może zmienić ŻADNEJ własność zanim wezwał inny inicjator - natomiast wyznaczony inicjator musi zainicjować właściwości, które zostały wprowadzone przez obecną klasę przed wywołaniem kolejnej inicjatora.Używając
convenience
słowa kluczowego, kompilator Swift wie, że musi sprawdzić te warunki - w przeciwnym razie nie mógłby.źródło
convenience
słowa kluczowego.let
właściwości). Nie może zainicjować właściwości. Wyznaczony inicjator jest odpowiedzialny za zainicjowanie wszystkich wprowadzonych właściwości przed wywołaniemsuper
wyznaczonego inicjatora.Klasa może mieć więcej niż jeden wyznaczony inicjator. Wygodny inicjator to pomocniczy inicjator, który musi wywołać wyznaczony inicjator tej samej klasy.
źródło