Więc zaktualizowałem dzisiaj do Xcode 6 beta 5 i zauważyłem, że otrzymałem błędy w prawie wszystkich moich podklasach klas Apple.
Błąd stwierdza:
Klasa „x” nie implementuje wymaganych elementów składowych swojej nadklasy
Oto jeden przykład, który wybrałem, ponieważ ta klasa jest obecnie dość lekka, więc będzie łatwa do opublikowania.
class InfoBar: SKSpriteNode { //Error message here
let team: Team
let healthBar: SKSpriteNode
init(team: Team, size: CGSize) {
self.team = team
if self.team == Team.TeamGood {
healthBar = SKSpriteNode(color: UIColor.greenColor(), size:size)
}
else {
healthBar = SKSpriteNode(color: UIColor.redColor(), size:size)
}
super.init(texture:nil, color: UIColor.darkGrayColor(), size: size)
self.addChild(healthBar)
}
}
Moje pytanie brzmi: dlaczego otrzymuję ten błąd i jak mogę go naprawić? Czego nie wdrażam? Dzwonię do wyznaczonego inicjatora.
źródło
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.init(collection:MPMediaItemCollection!)
. To pozwoliłobyinit(coder:)
przejść zero. Ale wtedy zdałem sobie sprawę: nie, teraz tylko oszukujesz kompilator. Podanie zero jest niedopuszczalne, więc rzućfatalError
i idź dalej. :)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ć.
required
słowa kluczowego Swift .init
metod.Tl; dr jest taka:
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:
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 2 nie jest szczególnie istotne dla tej rozmowy, ponieważ
SKSpriteNode
jestinit(coder: NSCoder)
to mało prawdopodobne, aby być metodą wygoda.Tak więc Twoja
InfoBar
klasa dziedziczyłarequired
inicjator aż do momentu dodaniainit(team: Team, size: CGSize)
.Jeśli było nie dostarczyli tę
init
metodę i zamiast dokonaniuInfoBar
„s dodanych właściwości opcjonalne lub pod warunkiem ich wartości domyślnych, to bym nie został jeszcze dziedziczenieSKSpriteNode
” sinit(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:
Który przedstawia następujący błąd:
Gdyby to było Objective-C, nie byłoby problemu z dziedziczeniem. Gdybyśmy zainicjowany
Bar
zeinitWithFoo:
w Objective-C,self.bar
nieruchomość byłoby po prostunil
. 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
„sinit(foo: String, bar: String)
, takich jak: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 :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 jestrequired
?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 specjalnychinit
metod (takich jakNSCoding
) wymaga, aby klasa oznaczała teinit
metody jakorequired
.Rozważmy ten przykład:
To się nie kompiluje. Generuje następujące ostrzeżenie:
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ż doInitProtocol
. 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 zinit
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
metodyrequired
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-CinstanceType
). Ale aby to zrobić, muszę użyćrequired
metody inicjalizacji.To generuje błąd:
W zasadzie to ten sam problem. Jeśli stworzymy podklasy
Box
, nasze podklasy odziedziczą metodę klasyfactory
. Więc mogliśmy zadzwonićSubclassedBox.factory()
. Jednak bezrequired
hasła nainit(size:)
metodzieBox
„s podklasy nie są gwarantowane do dziedziczeniaself.init(size:)
, któryfactory
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 mielirequired
metodę inicjalizującą i napotkamy dokładnie te same problemy, które napotkaliście tutaj zNSCoding
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 odziedziczymyrequired
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:To się nie kompiluje.
Komunikat o błędzie, który wyświetla, jest trochę mylący:
Ale chodzi o to,
Bar
nie dziedziczy każdy zFoo
„sinit
metod, ponieważ nie został spełniony jeden z dwóch przypadków szczególnych dziedziczyinit
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.źródło
Dlaczego pojawił się ten problem? Cóż, faktem jest, że zawsze ważne było (tj. W Objective-C, od dnia, w którym zacząłem programować Cocoa w Mac OS X 10.0), aby radzić sobie z inicjatorami, do obsługi których Twoja klasa nie jest przygotowana. Doktorzy zawsze jasno określali twoje obowiązki w tym zakresie. Ale ilu z nas zadało sobie trud, aby je wypełnić, całkowicie i co do joty? Chyba nikt z nas! Kompilator ich nie wymusił; to wszystko było czysto konwencjonalne.
Na przykład w mojej podklasie kontrolera widoku Objective-C z tym wyznaczonym inicjatorem:
... ważne jest, aby przekazać nam rzeczywistą kolekcję elementów multimedialnych: bez niej instancja po prostu nie może powstać. Ale nie napisałem żadnego „korka”, aby uniemożliwić komuś zainicjowanie mnie gołymi kośćmi
init
. I powinien być napisany jeden (faktycznie, właściwie mówiąc, powinienem pisemnej implementacjęinitWithNibName:bundle:
, odziedziczone wyznaczony initializer); ale byłem zbyt leniwy, żeby zawracać sobie głowę, ponieważ „wiedziałem”, że nigdy nie zainicjuję nieprawidłowo własnej klasy w ten sposób. To pozostawiło ziejącą dziurę. W Objective-C ktoś może zadzwonić do gołych kościinit
, pozostawiając moje ivary niezainicjowane, a my płyniemy w górę strumienia bez wiosła.Szybki, cudowny, w większości przypadków ratuje mnie od siebie. Jak tylko przetłumaczyłem tę aplikację na Swift, cały problem zniknął. Swift skutecznie tworzy dla mnie korek! Jeśli
init(collection:MPMediaItemCollection)
jest to jedyny wyznaczony inicjator zadeklarowany w mojej klasie, nie mogę zostać zainicjowany przez wywołanie bare-bonesinit()
. To cud!To, co wydarzyło się w seedie 5, to po prostu to, że kompilator zdał sobie sprawę, że cud nie działa w przypadku
init(coder:)
, ponieważ teoretycznie instancja tej klasy może pochodzić z końcówki, a kompilator nie może temu zapobiec - i kiedy obciążenia stalówki,init(coder:)
zostaną nazwane. Zatem kompilator wymusza jawne napisanie stopera. I też całkiem słusznie.źródło
fatalError
stoper opisany na stackoverflow.com/a/25128815/341994 . Po prostu zrób z niego fragment kodu użytkownika i od teraz możesz go po prostu wstawić tam, gdzie jest potrzebny. Trwa pół sekundy.Dodaj
źródło