Klasa nie implementuje wymaganych członków swojej superklasy

Więc zaktualizowałem do Xcode 6 beta 5 dzisiaj i zauważyłem, że otrzymałem błędy w prawie wszystkich moich podklas klas Apple.

Błąd stwierdza:

Klasa " x " nie implementuje wymaganych członków swojej superklasy

Oto jeden przykład, który wybrałem, ponieważ ta klasa jest obecnie dość lekka, więc łatwo będzie ją opublikować.

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)

    }

}
Więc moje pytanie brzmi: dlaczego otrzymuję ten błąd i jak mogę go naprawić? Czym nie jestem implementacja? Dzwonię do wyznaczonego inicjatora.
Author: Epic Byte, 2014-08-04

4 answers

[[4]}od pracownika Apple na forach programistów:

" sposób na zadeklarowanie kompilatorowi i zbudowanemu programowi, że naprawdę nie chcę być kompatybilny z Nscodingiem, to zrobić coś takiego: "

required init(coder: NSCoder) {
  fatalError("NSCoding not supported")
}

Jeśli wiesz, że nie chcesz być zgodny z NSCoding, jest to opcja. Podjąłem to podejście z dużą ilością mojego kodu SpriteKit, ponieważ Wiem, że nie będę go ładował z storyboardu.


Inną opcją, którą możesz wziąć, która działa dość dobrze, jest zaimplementuj metodę jako wygodny init, w ten sposób:

convenience required init(coder: NSCoder) {
    self.init(stringParam: "", intParam: 5)
}

Zwróć uwagę na wywołanie inicjalizatora w self. Pozwala to na użycie tylko wartości atrapy dla parametrów, w przeciwieństwie do wszystkich nieobowiązkowych właściwości, unikając rzucania błędu krytycznego.


Trzecią opcją jest oczywiście zaimplementowanie metody podczas wywoływania super i zainicjalizowanie wszystkich nieobowiązkowych właściwości. Należy zastosować takie podejście, jeśli obiekt jest widokiem ładowanym z storyboard:

required init(coder aDecoder: NSCoder!) {
    foo = "some string"
    bar = 9001

    super.init(coder: aDecoder)
}
 128
Author: Ben Kane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-14 14:24:13

Istnieją dwa absolutniekluczowe fragmenty informacji specyficznych dla Swift, których brakuje w istniejących odpowiedziach, które myślę, że pomagają to całkowicie wyjaśnić.

  1. jeśli protokół określa inicjalizator jako wymaganą metodę, to inicjalizator musi być oznaczony za pomocą słowa kluczowego Swift required.
  2. Swift ma specjalny zestaw reguł dziedziczenia dotyczących metod init.

Tl;dr jest to:

Jeśli wdrożysz jakiekolwiek inicjalizatory, nie dziedziczysz już żadnego z wyznaczonych inicjalizatorów klasy nadrzędnej.

jedynymi inicjalizatorami, jeśli istnieją, które odziedziczysz, są inicjalizatory SUPER class convenience, które wskazują na wyznaczony inicjalizator, który przypadkiem nadpisałeś.

Więc... gotowa na długą wersję?


Swift ma specjalny zestaw reguł dziedziczenia dotyczących metod init.

Wiem, że to był drugi z dwóch moich punktów, ale nie możemy zrozumieć pierwszy punkt lub dlaczego słowo kluczowe required istnieje, dopóki nie zrozumiemy tego punktu. Kiedy zrozumiemy ten punkt, drugi staje się dość oczywisty.

wszystkie informacje zawarte w tej sekcji odpowiedzi pochodzą z dokumentacji Apple znalezionej tutaj.

Z dokumentów Apple:

W przeciwieństwie do podklas w Objective-C, podklasy Swift domyślnie nie dziedziczą swoich superklas inicjalizatorów. podejście Swifta zapobiega sytuacji, w której prosty inicjator z klasy nadrzędnej 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.

/ align = "left" /

Tak więc, prosto z dokumentów Apple, widzimy, że podklasy Swift nie zawsze (i zazwyczaj nie) dziedziczą metody init ich superklasy.

Kiedy więc dziedziczą po swoich superklasa?

Istnieją dwie reguły, które określają, kiedy podklasa dziedziczy metody {[6] } od swojego rodzica. Z Apple docs:

Artykuł 1

Jeśli podklasa nie definiuje żadnych wyznaczonych inicjalizatorów, automatycznie dziedziczy wszystkie swoje nadklasy wyznaczone inicjalizatory.

Artykuł 2

Jeśli twoja podklasa udostępnia implementację wszystkich jej superklas oznaczonych inicjalizatorów-albo dziedzicząc je zgodnie z regułą 1, lub dostarczając niestandardową implementację jako część jej definicji - wtedy automatycznie dziedziczy wszystkie superclass convenience initializers.

Zasada 2 nie jest szczególnie istotna w tej rozmowie, ponieważ SKSpriteNode'S init(coder: NSCoder) jest mało prawdopodobne, aby być wygodną metodą.

Więc twoja klasa InfoBar dziedziczyła inicjalizator required aż do momentu, w którym dodałeś init(team: Team, size: CGSize).

Jeśli nie podałeś tej metody init, a zamiast tego dokonałeś InfoBar's dodane właściwości opcjonalne lub pod warunkiem, że z wartościami domyślnymi, wtedy nadal dziedziczy SKSpriteNode'S init(coder: NSCoder). Jednak, kiedy dodaliśmy własny niestandardowy inicjalizator, przestaliśmy dziedziczyć wyznaczone przez naszą superklasę inicjalizatory (i wygodne inicjalizatory, które nie wskazywały na zaimplementowane przez nas inicjalizatory).

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 argumentów za parametr 'bar' w wywołaniu.

Tutaj wpisz opis obrazka

Gdyby to było Objective-C, nie byłoby problemu z dziedziczeniem. Jeśli zainicjalizujemy Bar z initWithFoo: w Objective-C, właściwość self.bar będzie po prostu nil. Prawdopodobnie nie jest to świetne, ale jest to doskonale poprawny stan, w którym obiekt się znajduje. To jest , a nie Stan idealny dla obiektu Swift. self.bar nie jest opcjonalne i nie może być nil.

/ Align = "left" / inherit initializers jest przez nie dostarczanie własnych. Jeśli więc spróbujemy dziedziczyć przez usunięcie Bar ' s init(foo: String, bar: String), jako takie:
class Bar: Foo {
    var bar: String
}

Teraz wracamy do dziedziczenia (tak jakby), ale to nie będzie kompilowane... a Komunikat o błędzie wyjaśnia dokładnie, dlaczego nie dziedziczymy metod klasy superclass init:

Problem: Klasa ' Bar ' nie ma inicjalizatorów

Fix-It: przechowywana właściwość ' bar ' bez inicjalizatorów zapobiega syntetyzowanym inicjalizatorom

Jeśli dodaliśmy przechowywane właściwości w naszej podklasie, nie ma możliwego szybkiego sposobu na utworzenie poprawnej instancji naszej podklasy z inicjalizatorami superclass, które nie mogłyby wiedzieć o przechowywanych właściwościach naszej podklasy.


/ Align = "left" / Dlaczego required? initmetody Swifta mogą działać według specjalnego zestawu reguł dziedziczenia, ale zgodność z protokołem 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 jest to problemem, ponieważ większość protokołów wymaga tylko metod, które nie działają według specjalnych reguł dziedziczenia w języku Swift, więc jeśli dziedziczysz z klasy, która jest zgodna z protokołem, dziedziczysz również wszystkie metody lub właściwości, które pozwalają klasie spełniać wymagania protokołu.

Pamiętaj jednak, że metody Swifta działają według specjalnych zasad i nie zawsze są dziedziczone. Z tego powodu, Klasa zgodna z protokołem wymagającym specjalnych metod init (takich jak NSCoding) wymaga, aby Klasa oznaczała te metody init jako required.

Rozważ 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 ono następujące ostrzeżenie:

Problem: wymóg inicjalizacji 'INIT (foo:)' może być spełniony tylko przez 'wymagany' inicjalizator w niekończącej się klasie 'ConformingClass'

Fix-It: Insert required

Chce ja, aby init(foo: Int) inicjalizator wymagane. Mogę również sprawić, że będzie to szczęśliwe, tworząc klasę final (co oznacza, że klasa nie może być dziedziczona).

Więc, co się stanie, jeśli podklasuję? Od tego momentu, Jeśli będę podklasą, nic mi nie będzie. Jeśli jednak dodam jakieś inicjalizatory, nagle przestaję dziedziczyć init(foo:). Jest to problematyczne, ponieważ teraz nie jestem już zgodny z InitProtocol. Nie mogę podklasować klasy, która jest zgodna z protokołem i nagle zdecydować, że nie chcę już być zgodna z ten protokół. Odziedziczyłem zgodność Protokołu, Ale ze względu na sposób, w jaki Swift działa z dziedziczeniem metody init, nie odziedziczyłem części tego, co jest wymagane do zgodności z tym protokołem i muszę go zaimplementować.


To ma sens. Ale dlaczego nie mogę uzyskać bardziej pomocnego Komunikatu o błędzie?

Prawdopodobnie komunikat o błędzie może być bardziej jasny lub lepszy, jeśli określi, że twoja klasa nie jest już zgodna z dziedziczonym protokołem NSCoding i że aby to naprawić, potrzeba wdrożenia init(coder: NSCoder). Jasne.

Ale Xcode po prostu nie może wygenerować tego komunikatu, ponieważ nie zawsze będzie to rzeczywisty problem z nie implementacją lub dziedziczeniem wymaganej metody. Jest co najmniej jeden inny powód, aby init metody required oprócz zgodności protokołu, a są to metody fabryczne.

Jeśli chcę napisać poprawną metodę fabryczną, muszę określić typ zwracany Self (odpowiednik Swifta instanceType Objective-C). Ale aby to zrobić, Muszę użyć metody inicjalizacji required.

class Box {
    var size: CGSize
    init(size: CGSize) {
        self.size = size
    }

    class func factory() -> Self {
        return self.init(size: CGSizeZero)
    }
}

To generuje błąd:

Jest to obiekt klasy "Self" z wartością metatype, który musi być użyty do inicjalizacji "required".]}

Tutaj wpisz opis obrazka

To w zasadzie ten sam problem. Jeśli podklasa Box, nasze podklasy odziedziczą metodę klasy factory. Więc możemy zadzwonić SubclassedBox.factory(). Jednak bez słowa kluczowego required w metodzie init(size:), podklasy Box nie są gwarantowane dziedziczenie self.init(size:), że factory dzwoni.

Więc musimy stworzyć tę metodę required jeśli chcemy mieć metodę fabryczną taką jak ta, A to oznacza, że jeśli Nasza klasa implementuje metodę taką jak ta, będziemy mieli metodę inicjalizacyjną required i napotkamy dokładnie te same problemy, które napotkaliśmy tutaj z protokołem NSCoding.


Ostatecznie wszystko sprowadza się do podstawowego zrozumienia, że inicjalizatory Swifta działają według nieco innego zestawu reguł dziedziczenia, co oznacza nie masz gwarancji dziedziczenia inicjalizatorów ze swojej klasy nadrzędnej. Dzieje się tak, ponieważ inicjalizatory klasy superclass nie mogą wiedzieć o nowych przechowywanych właściwościach i nie mogą utworzyć instancji obiektu do poprawnego stanu. Ale z różnych powodów Klasa superclass może oznaczać inicjalizator jako required. Kiedy tak się stanie, możemy albo zastosować jeden z bardzo specyficznych scenariuszy, według których rzeczywiście dziedziczymy metodę required, albo musimy ją wdrożyć sami.

Głównym punktem tutaj jest jednak to, że jeśli otrzymujemy błąd, który tutaj widzisz, oznacza to, że twoja klasa w ogóle nie implementuje tej metody.

Jako być może jeden z ostatnich przykładów na to, że podklasy Swift nie zawsze dziedziczą metody rodzica init (co moim zdaniem jest absolutnie kluczowe dla pełnego zrozumienia tego problemu), rozważ ten przykład: {71]}

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 skompiluje.

Tutaj wpisz opis obrazka

Komunikat o błędzie jest trochę mylący:

Dodatkowy argument " b " w wywołaniu

Ale chodzi o to, Bar Nie dziedziczy żadnej z metod Foo, ponieważ nie spełnia żadnego z dwóch specjalnych przypadków dziedziczenia metod init ze swojej macierzystej klasy.

Gdyby to było Objective-C, dziedziczylibyśmy to init bez problemu, ponieważ Objective-C jest całkowicie zadowolony z tego, że nie inicjalizuje właściwości obiektów (choć jako programista nie powinieneś był być zadowolony z tego). W Swift, To po prostu nie zrobi. Nie możesz mieć nieprawidłowego stanu, a dziedziczenie superclass initializers może prowadzić tylko do nieprawidłowych Stanów obiektów.

 73
Author: nhgrif,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Dlaczego pojawił się ten problem? Cóż, oczywistym faktem jest to, że zawsze było ważne (np. w Objective-C, od dnia, w którym zacząłem programować Cocoa w Mac OS X 10.0), aby poradzić sobie z inicjalizatorami, których twoja klasa nie jest przygotowana do obsługi. Dokumenty zawsze jasno mówiły o twoich obowiązkach w tym zakresie. Ale ilu z nas zadało sobie trud, aby je wypełnić, całkowicie i do listu? Pewnie nikt z nas! I kompilator ich nie egzekwował; wszystko to było czysto konwencjonalne.

Na przykład, w podklasie kontrolera widoku Objective-C z oznaczonym inicjalizatorem:

- (instancetype) initWithCollection: (MPMediaItemCollection*) coll;

...ważne jest, aby przekazać nam rzeczywisty zbiór przedmiotów medialnych: instancja po prostu nie może powstać bez niego. Ale nie napisałem żadnego "stoppera", aby uniemożliwić komuś inicjowanie mnie gołymi kośćmi init. I should have written one (właściwie, właściwie mówiąc, powinienem był napisać implementację initWithNibName:bundle:, dziedziczone desygnated initializer) ; ale byłem zbyt leniwy, aby zawracać sobie głowę, ponieważ "wiedziałem", że nigdy nie będę nieprawidłowo inicjował własnej klasy w ten sposób. To zostawiło dziurawą dziurę. W Objective-C, ktośmoże zadzwonić do bare-bones init, pozostawiając moje ivars niezaliczone, i jesteśmy w górze strumienia bez wiosła.

Swift, cudownie, ratuje mnie przed sobą w większości przypadków. 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 tylko wyznaczony inicjalizator zadeklarowany w mojej klasie, nie mogę być inicjowany przez wywołanie bare-bones init(). To cud!

To, co stało się w seed 5, to tylko to, że kompilator zdał sobie sprawę, że cud nie działa w przypadku init(coder:), ponieważ teoretycznie instancja tej klasy może pochodzić z stalówki, a kompilator nie może temu zapobiec - a kiedy stalówka ładuje się, init(coder:) zostanie wywołana. Więc kompilator sprawia, że piszesz korek wprost. I słusznie.

 56
Author: matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-05 19:27:26

Dodaj

required init(coder aDecoder: NSCoder!) {
  super.init(coder: aDecoder)
}
 33
Author: Gagan Singh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-04 19:48:49