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ą
requiredsłowa kluczowego Swift .
- Swift ma specjalny zestaw reguł dziedziczenia dotyczących
initmetod.
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 initmetod.
Wiem, że była to druga z dwóch poruszonych przeze mnie kwestii, ale nie możemy zrozumieć pierwszej kwestii ani dlaczego requiredsł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ą) initmetod swojej nadklasy .
Kiedy więc dziedziczą po swojej superklasie?
Istnieją dwie reguły, które definiują, kiedy podklasa dziedziczy initmetody 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ż SKSpriteNodejest init(coder: NSCoder)to mało prawdopodobne, aby być metodą wygoda.
Tak więc Twoja InfoBarklasa dziedziczyła requiredinicjator aż do momentu dodania init(team: Team, size: CGSize).
Jeśli było nie dostarczyli tę initmetodę 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 Barze initWithFoo:w Objective-C, self.barnieruchomość 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.barNie 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 initmetod 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?
initMetody 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 initmetody Swifta działają według specjalnego zestawu reguł i nie zawsze są dziedziczone. Z tego powodu klasa zgodna z protokołem wymagającym specjalnych initmetod (takich jak NSCoding) wymaga, aby klasa oznaczała te initmetody 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 initdziedziczeniem 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 NSCodingprotokoł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ć initmetody requiredpoza 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ć requiredmetody 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 requiredhasła na init(size:)metodzie Box„s podklasy nie są gwarantowane do dziedziczenia self.init(size:), który factorydzwoni.
Więc musimy stworzyć tę metodę, requiredjeśli chcemy taką metodę fabryczną, a to oznacza, że jeśli nasza klasa implementuje taką metodę, będziemy mieli requiredmetodę inicjalizującą i napotkamy dokładnie te same problemy, które napotkaliście tutaj z NSCodingprotokoł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 requiredmetodę, 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ą initmetody 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, Barnie dziedziczy każdy z Foo„s initmetod, ponieważ nie został spełniony jeden z dwóch przypadków szczególnych dziedziczy initmetody z tej klasy nadrzędnej.
Gdyby to było Objective-C, odziedziczylibyśmy to initbez 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; tylkofatalErrorpodejście jest właściwe.