Istnieją dwie absolutnie kluczowe informacje dotyczące języka Swift, których brakuje w istniejących odpowiedziach, które moim zdaniem pomogą całkowicie to wyjaśnić.
- Jeśli protokół określa inicjator jako wymaganą metodę, ten inicjator musi być oznaczony za pomocą
required
słowa kluczowego Swift .
- Swift ma specjalny zestaw reguł dziedziczenia dotyczących
init
metod.
Tl; dr jest taka:
Jeśli zaimplementujesz jakiekolwiek inicjatory, nie będziesz już dziedziczyć żadnego z wyznaczonych inicjatorów nadklasy.
Jedynymi inicjatorami, jeśli istnieją, które będziesz dziedziczyć, są inicjatory super klasy wygody, które wskazują na wyznaczony inicjator, który zdarzyło się przesłonić.
Więc ... gotowy na długą wersję?
Swift ma specjalny zestaw reguł dziedziczenia dotyczących init
metod.
Wiem, że była to druga z dwóch poruszonych przeze mnie kwestii, ale nie możemy zrozumieć pierwszej kwestii ani dlaczego required
słowo kluczowe istnieje, dopóki nie zrozumiemy tej kwestii. Kiedy zrozumiemy ten punkt, drugi staje się dość oczywisty.
Wszystkie informacje, które omówię w tej sekcji tej odpowiedzi, pochodzą z dokumentacji firmy Apple, która znajduje się tutaj .
Z dokumentów Apple:
W przeciwieństwie do podklas w Objective-C, podklasy Swift nie dziedziczą domyślnie inicjatorów nadklasy. Podejście Swifta zapobiega sytuacji, w której prosty inicjator z nadklasy jest dziedziczony przez bardziej wyspecjalizowaną podklasę i jest używany do tworzenia nowej instancji podklasy, która nie jest w pełni lub poprawnie zainicjowana.
Podkreśl moje.
Tak więc, prosto z dokumentów Apple, widzimy, że podklasy Swift nie zawsze (i zwykle nie dziedziczą) init
metod swojej nadklasy .
Kiedy więc dziedziczą po swojej superklasie?
Istnieją dwie reguły, które definiują, kiedy podklasa dziedziczy init
metody po swoim rodzicu. Z dokumentów Apple:
Zasada nr 1
Jeśli twoja podklasa nie definiuje żadnych wyznaczonych inicjatorów, automatycznie dziedziczy wszystkie wyznaczone inicjatory nadklasy.
Zasada 2
Jeśli 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.
Zasada 2 nie jest szczególnie istotne dla tej rozmowy, ponieważ SKSpriteNode
jest init(coder: NSCoder)
to mało prawdopodobne, aby być metodą wygoda.
Tak więc Twoja InfoBar
klasa dziedziczyła required
inicjator aż do momentu dodania init(team: Team, size: CGSize)
.
Jeśli było nie dostarczyli tę init
metodę i zamiast dokonaniu InfoBar
„s dodanych właściwości opcjonalne lub pod warunkiem ich wartości domyślnych, to bym nie został jeszcze dziedziczenie SKSpriteNode
” s init(coder: NSCoder)
. Jednak kiedy dodaliśmy nasz własny niestandardowy inicjator, przestaliśmy dziedziczyć wyznaczone przez nas inicjatory naszej nadklasy (i wygodne inicjatory, które nie wskazywały na zaimplementowane przez nas inicjatory).
Tak więc, jako uproszczony przykład, przedstawiam to:
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")
Który przedstawia następujący błąd:
Brak argumentu dla parametru „bar” w wywołaniu.
Gdyby to było Objective-C, nie byłoby problemu z dziedziczeniem. Gdybyśmy zainicjowany Bar
ze initWithFoo:
w Objective-C, self.bar
nieruchomość byłoby po prostu nil
. Prawdopodobnie nie jest świetny, ale jest to całkowicie poprawny stan obiektu. Stan obiektu Swift nie jest całkowicie poprawny. self.bar
Nie jest opcjonalny i nie może być nil
.
Ponownie, jedynym sposobem, w jaki możemy dziedziczyć inicjatory, jest nie dostarczanie własnych. Tak więc, jeśli spróbujemy dziedziczyć usuwając Bar
„s init(foo: String, bar: String)
, takich jak:
class Bar: Foo {
var bar: String
}
Teraz wracamy do dziedziczenia (w pewnym sensie), ale to się nie skompiluje ... a komunikat o błędzie wyjaśnia dokładnie, dlaczego nie dziedziczymy init
metod nadklasy :
Problem: klasa „Bar” nie ma inicjatorów
Fix-It: Stored property „bar” bez inicjatorów zapobiega syntezowaniu inicjatorów
Jeśli dodaliśmy przechowywane właściwości w naszej podklasie, nie ma możliwości szybkiego utworzenia prawidłowego wystąpienia naszej podklasy z inicjatorami nadklasy, które prawdopodobnie nie mogą wiedzieć o przechowywanych właściwościach naszej podklasy.
Ok, cóż, dlaczego w ogóle muszę to wdrażać init(coder: NSCoder)
? Dlaczego tak jest required
?
init
Metody Swifta mogą działać według specjalnego zestawu reguł dziedziczenia, ale zgodność protokołu jest nadal dziedziczona w łańcuchu. Jeśli klasa nadrzędna jest zgodna z protokołem, jej podklasy muszą być zgodne z tym protokołem.
Zwykle nie stanowi to problemu, ponieważ większość protokołów wymaga tylko metod, które nie działają zgodnie ze specjalnymi regułami dziedziczenia w języku Swift, więc jeśli dziedziczysz z klasy zgodnej z protokołem, dziedziczysz również wszystkie metody lub właściwości, które pozwalają klasie na spełnienie wymagań protokołu.
Pamiętaj jednak, że init
metody Swifta działają według specjalnego zestawu reguł i nie zawsze są dziedziczone. Z tego powodu klasa zgodna z protokołem wymagającym specjalnych init
metod (takich jak NSCoding
) wymaga, aby klasa oznaczała te init
metody jako required
.
Rozważmy ten przykład:
protocol InitProtocol {
init(foo: Int)
}
class ConformingClass: InitProtocol {
var foo: Int
init(foo: Int) {
self.foo = foo
}
}
To się nie kompiluje. Generuje następujące ostrzeżenie:
Problem: Wymaganie inicjatora „init (foo :)” może być spełnione tylko przez „wymagany” inicjator w nieostatniej klasie „ConformingClass”
Fix-It: Wymagana wstawka
Chce, żebym init(foo: Int)
wymagał inicjalizacji. Mógłbym również uczynić to szczęśliwym, tworząc klasę final
(co oznacza, że klasa nie może być dziedziczona po).
Więc co się stanie, jeśli podklasę? Od tego momentu, jeśli podklasy, wszystko w porządku. Jeśli jednak dodam jakiekolwiek inicjatory, nagle przestaję dziedziczyć init(foo:)
. Jest to problematyczne, ponieważ teraz nie dostosowuję się już do InitProtocol
. Nie mogę utworzyć podklasy z klasy, która jest zgodna z protokołem, a potem nagle decyduję, że nie chcę już dostosowywać się do tego protokołu. Odziedziczyłem zgodność protokołu, ale ze względu na sposób, w jaki Swift działa z init
dziedziczeniem metod, nie odziedziczyłem części tego, co jest wymagane do zgodności z tym protokołem i muszę to zaimplementować.
Dobra, to wszystko ma sens. Ale dlaczego nie mogę otrzymać bardziej pomocnego komunikatu o błędzie?
Prawdopodobnie komunikat o błędzie może być wyraźniejszy lub lepszy, jeśli określa, że Twoja klasa nie jest już zgodna z odziedziczonym NSCoding
protokołem i że aby to naprawić, musisz zaimplementować init(coder: NSCoder)
. Pewnie.
Ale Xcode po prostu nie może wygenerować tej wiadomości, ponieważ tak naprawdę nie zawsze będzie to faktyczny problem z brakiem implementacji lub dziedziczenia wymaganej metody. Jest jeszcze jeden powód, dla którego warto tworzyć init
metody required
poza zgodnością z protokołem, a są to metody fabryczne.
Jeśli chcę napisać odpowiednią metodę fabryczną, muszę określić typ zwracanej wartości Self
(odpowiednik języka Swift z Objective-C instanceType
). Ale aby to zrobić, muszę użyć required
metody inicjalizacji.
class Box {
var size: CGSize
init(size: CGSize) {
self.size = size
}
class func factory() -> Self {
return self.init(size: CGSizeZero)
}
}
To generuje błąd:
Konstruowanie obiektu klasy „Self” z wartością metatype wymaga użycia „wymaganego” inicjatora
W zasadzie to ten sam problem. Jeśli stworzymy podklasy Box
, nasze podklasy odziedziczą metodę klasy factory
. Więc mogliśmy zadzwonić SubclassedBox.factory()
. Jednak bez required
hasła na init(size:)
metodzie Box
„s podklasy nie są gwarantowane do dziedziczenia self.init(size:)
, który factory
dzwoni.
Więc musimy stworzyć tę metodę, required
jeśli chcemy taką metodę fabryczną, a to oznacza, że jeśli nasza klasa implementuje taką metodę, będziemy mieli required
metodę inicjalizującą i napotkamy dokładnie te same problemy, które napotkaliście tutaj z NSCoding
protokołem.
Ostatecznie wszystko sprowadza się do podstawowego zrozumienia, że inicjatory Swift działają według nieco innego zestawu reguł dziedziczenia, co oznacza, że nie masz gwarancji dziedziczenia inicjatorów z nadklasy. Dzieje się tak, ponieważ inicjatory nadklasy nie mogą wiedzieć o nowych przechowywanych właściwościach i nie mogą utworzyć instancji obiektu w prawidłowym stanie. Jednak z różnych powodów nadklasa może oznaczać inicjator jako required
. Kiedy tak się stanie, możemy albo zastosować jeden z bardzo konkretnych scenariuszy, dzięki którym faktycznie odziedziczymy required
metodę, albo musimy wdrożyć ją samodzielnie.
Najważniejsze jest jednak to, że jeśli otrzymujemy błąd, który widzisz tutaj, oznacza to, że Twoja klasa w rzeczywistości w ogóle nie implementuje metody.
Jako być może ostatni przykład pokazujący, że podklasy Swift nie zawsze dziedziczą init
metody swoich rodziców (co moim zdaniem jest absolutnie kluczowe dla pełnego zrozumienia tego problemu), rozważ następujący przykład:
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)
To się nie kompiluje.
Komunikat o błędzie, który wyświetla, jest trochę mylący:
Dodatkowy argument „b” w wywołaniu
Ale chodzi o to, Bar
nie dziedziczy każdy z Foo
„s init
metod, ponieważ nie został spełniony jeden z dwóch przypadków szczególnych dziedziczy init
metody z tej klasy nadrzędnej.
Gdyby to było Objective-C, odziedziczylibyśmy to init
bez problemu, ponieważ Objective-C jest całkowicie szczęśliwy, nie inicjując właściwości obiektów (chociaż jako programista nie powinieneś być z tego zadowolony). W Swift to po prostu nie wystarczy. Nie możesz mieć nieprawidłowego stanu, a dziedziczenie inicjatorów nadklasy może prowadzić tylko do nieprawidłowych stanów obiektów.
init(collection:MPMediaItemCollection)
. Musisz dostarczyć prawdziwą kolekcję elementów multimedialnych; o to chodzi w tej klasie. Ta klasa po prostu nie może zostać utworzona bez jednej. Zamierza przeanalizować kolekcję i zainicjować kilkanaście zmiennych instancji. O to właśnie chodzi w byciu jedynym wyznaczonym inicjatorem! Dlategoinit(coder:)
nie ma żadnego sensownego (lub nawet bezsensownego) MPMediaItemCollection do dostarczenia tutaj; tylkofatalError
podejście jest właściwe.