Deshalb habe ich heute auf Xcode 6 Beta 5 aktualisiert und festgestellt, dass ich in fast allen meiner Unterklassen der Apple-Klassen Fehler erhalten habe.
Der Fehler lautet:
Die Klasse 'x' implementiert nicht die erforderlichen Mitglieder ihrer Oberklasse
Hier ist ein Beispiel, das ich ausgewählt habe, da diese Klasse derzeit ziemlich leicht ist und daher leicht zu posten ist.
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)
}
}
Meine Frage ist also, warum ich diesen Fehler erhalte und wie ich ihn beheben kann. Was implementiere ich nicht? Ich rufe einen bestimmten Initialisierer auf.
quelle
init(collection:MPMediaItemCollection)
. Sie müssen eine echte Sammlung von Medienelementen bereitstellen. das ist der Punkt dieser Klasse. Diese Klasse kann einfach nicht ohne eine instanziiert werden. Es wird die Sammlung analysieren und ein Dutzend Instanzvariablen initialisieren. Das ist der springende Punkt, wenn dies der einzige designierte Initialisierer ist! Daher gibt esinit(coder:)
hier keine aussagekräftige (oder sogar bedeutungslose) MPMediaItemCollection; Nur derfatalError
Ansatz ist richtig.init(collection:MPMediaItemCollection!)
. Das würde erlaubeninit(coder:)
, null zu passieren. Aber dann wurde mir klar: Nein, jetzt täuschen Sie nur den Compiler. Null zu übergeben ist nicht akzeptabel, also werfen Sie dasfatalError
und fahren Sie fort. :)In den vorhandenen Antworten fehlen zwei absolut wichtige Swift-spezifische Informationen, die meiner Meinung nach dazu beitragen, dies vollständig zu klären.
required
Schlüsselwort Swift markiert werden .init
Methoden.Das tl; dr ist das:
Die einzigen Initialisierer, die Sie erben werden, sind erstklassige Convenience-Initialisierer, die auf einen bestimmten Initialisierer verweisen, den Sie zufällig überschrieben haben.
Also ... bereit für die lange Version?
Swift hat spezielle Vererbungsregeln für
init
Methoden.Ich weiß, dass dies der zweite von zwei Punkten war, die ich angesprochen habe, aber wir können den ersten Punkt nicht verstehen oder warum das
required
Schlüsselwort überhaupt existiert, bis wir diesen Punkt verstanden haben. Sobald wir diesen Punkt verstanden haben, wird der andere ziemlich offensichtlich.Alle Informationen, die ich in diesem Abschnitt dieser Antwort behandele, stammen aus der Dokumentation von Apple, die Sie hier finden .
Aus den Apple-Dokumenten:
Hervorhebung von mir.
Direkt aus den Apple-Dokumenten sehen wir also, dass Swift-Unterklassen die
init
Methoden ihrer Oberklasse nicht immer (und normalerweise nicht) erben .Wann erben sie von ihrer Oberklasse?
Es gibt zwei Regeln, die definieren, wann eine Unterklasse
init
Methoden von ihrem übergeordneten Element erbt . Aus den Apple-Dokumenten:Regel 2 ist nicht besonders relevant für dieses Gespräch , weil
SKSpriteNode
‚sinit(coder: NSCoder)
ist unwahrscheinlich , dass eine bequeme Methode sein.Ihre
InfoBar
Klasse hat also denrequired
Initialisierer bis zu dem Punkt geerbt, den Sie hinzugefügt habeninit(team: Team, size: CGSize)
.Wenn Sie diese nicht zur Verfügung gestellt haben , waren
init
Verfahren und stattdessen auf IhreInfoBar
's hinzugefügt Eigenschaften optional oder versehen sie mit Standardwerten, dann würden Sie noch vererbt wordenSKSpriteNode
istinit(coder: NSCoder)
. Als wir jedoch unseren eigenen benutzerdefinierten Initialisierer hinzufügten, hörten wir auf, die festgelegten Initialisierer unserer Oberklasse zu erben (und praktische Initialisierer, die nicht auf von uns implementierte Initialisierer hinwiesen).Als vereinfachtes Beispiel präsentiere ich Folgendes:
Welches den folgenden Fehler darstellt:
Wenn dies Objective-C wäre, hätte es kein Problem zu erben. Wenn wir a
Bar
mitinitWithFoo:
in Objective-C initialisierenself.bar
würden , wäre die Eigenschaft einfachnil
. Es ist wahrscheinlich nicht großartig, aber es ist ein vollkommen gültiger Zustand für das Objekt. Es ist kein vollkommen gültiger Zustand für das Swift-Objekt. Esself.bar
ist nicht optional und kann nicht seinnil
.Wiederum erben wir Initialisierer nur, indem wir keine eigenen bereitstellen. Also , wenn wir versuchen , zu vererben durch Löschen
Bar
s‘init(foo: String, bar: String)
, wie zum Beispiel:Jetzt sind wir wieder beim Erben (irgendwie), aber das wird nicht kompiliert ... und die Fehlermeldung erklärt genau, warum wir keine
init
Methoden der Oberklasse erben :Wenn wir gespeicherte Eigenschaften in unsere Unterklasse aufgenommen haben, gibt es keine schnelle Möglichkeit, eine gültige Instanz unserer Unterklasse mit den Superklassen-Initialisierern zu erstellen, die möglicherweise nichts über die gespeicherten Eigenschaften unserer Unterklasse wissen.
Okay, warum muss ich überhaupt implementieren
init(coder: NSCoder)
? Warum ist esrequired
?Swifts
init
Methoden spielen möglicherweise nach einem speziellen Satz von Vererbungsregeln, aber die Protokollkonformität wird weiterhin in der gesamten Kette vererbt. Wenn eine übergeordnete Klasse einem Protokoll entspricht, müssen ihre Unterklassen diesem Protokoll entsprechen.Normalerweise ist dies kein Problem, da für die meisten Protokolle nur Methoden erforderlich sind, die in Swift nicht nach speziellen Vererbungsregeln abgespielt werden. Wenn Sie also von einer Klasse erben, die einem Protokoll entspricht, erben Sie auch alle Methoden oder Eigenschaften, mit denen die Klasse die Protokollkonformität erfüllen kann.
Denken Sie jedoch daran, dass Swifts
init
Methoden nach einem bestimmten Regelwerk gespielt werden und nicht immer vererbt werden. Aus diesem Grund erfordert eine Klasse, die einem Protokoll entspricht, das spezielleinit
Methoden erfordert (z. B.NSCoding
), dass die Klasse dieseinit
Methoden als markiertrequired
.Betrachten Sie dieses Beispiel:
Dies wird nicht kompiliert. Es wird die folgende Warnung generiert:
Ich soll den
init(foo: Int)
erforderlichen Initialisierer erstellen. Ich könnte es auch glücklich machen, indem ich die Klasse machefinal
(was bedeutet, dass die Klasse nicht geerbt werden kann).Was passiert also, wenn ich eine Unterklasse habe? Ab diesem Punkt geht es mir gut, wenn ich eine Unterklasse habe. Wenn ich jedoch Initialisierer hinzufüge, erbe ich plötzlich nicht mehr
init(foo:)
. Das ist problematisch, weil ich mich jetzt nicht mehr an die anpasseInitProtocol
. Ich kann keine Unterklasse einer Klasse bilden, die einem Protokoll entspricht, und dann plötzlich entscheiden, dass ich diesem Protokoll nicht mehr entsprechen möchte. Ich habe die Protokollkonformität geerbt, aber aufgrund der Art und Weise, wie Swift mit derinit
Methodenvererbung arbeitet, habe ich keinen Teil dessen geerbt, was zur Konformität mit diesem Protokoll erforderlich ist, und ich muss es implementieren.Okay, das alles macht Sinn. Aber warum kann ich keine hilfreichere Fehlermeldung erhalten?
Möglicherweise ist die Fehlermeldung klarer oder besser, wenn angegeben wird, dass Ihre Klasse nicht mehr dem geerbten
NSCoding
Protokoll entspricht und dass Sie sie implementieren müssen, um sie zu behebeninit(coder: NSCoder)
. Sicher.Xcode kann diese Nachricht jedoch einfach nicht generieren, da dies nicht immer das eigentliche Problem ist, wenn eine erforderliche Methode nicht implementiert oder geerbt wird. Neben der Protokollkonformität gibt es noch einen weiteren Grund,
init
Methoden zurequired
erstellen, und das sind Factory-Methoden.Wenn ich eine richtige Factory-Methode schreiben möchte, muss ich den Rückgabetyp angeben
Self
(Swifts Äquivalent zu Objective-CsinstanceType
). Dazu muss ich jedoch einerequired
Initialisierungsmethode verwenden.Dies erzeugt den Fehler:
Es ist im Grunde das gleiche Problem. Wenn wir eine Unterklasse bilden
Box
, erben unsere Unterklassen die Klassenmethodefactory
. Also konnten wir anrufenSubclassedBox.factory()
. Ohne dasrequired
Schlüsselwort in derinit(size:)
Methode wirdBox
jedoch nicht garantiert,self.init(size:)
dassfactory
die Unterklassen des Aufrufs das erben , was aufgerufen wird.Wir müssen diese Methode also erstellen,
required
wenn wir eine Factory-Methode wie diese wollen. Wenn unsere Klasse einerequired
solche Methode implementiert, haben wir eine Initialisierungsmethode und stoßen auf genau dieselben Probleme, auf die Sie hier gestoßen sind mit demNSCoding
Protokoll.Letztendlich läuft alles auf das grundlegende Verständnis hinaus, dass Swifts Initialisierer nach etwas anderen Vererbungsregeln spielen, was bedeutet, dass Sie nicht garantiert Initialisierer von Ihrer Oberklasse erben. Dies liegt daran, dass Superklassen-Initialisierer Ihre neuen gespeicherten Eigenschaften nicht kennen und Ihr Objekt nicht in einen gültigen Zustand versetzen können. Aus verschiedenen Gründen kann eine Superklasse einen Initialisierer als markieren
required
. In diesem Fall können wir entweder eines der sehr spezifischen Szenarien verwenden, mit denen wir dierequired
Methode tatsächlich erben , oder wir müssen sie selbst implementieren.Der Hauptpunkt hier ist jedoch, dass wenn wir den Fehler sehen, den Sie hier sehen, dies bedeutet, dass Ihre Klasse die Methode überhaupt nicht implementiert.
init
Betrachten Sie dieses Beispiel als ein letztes Beispiel, um die Tatsache zu untersuchen, dass Swift-Unterklassen nicht immer die Methoden ihrer Eltern erben (was meiner Meinung nach für das vollständige Verständnis dieses Problems von zentraler Bedeutung ist):Dies kann nicht kompiliert werden.
Die Fehlermeldung ist etwas irreführend:
Aber der Punkt ist,
Bar
erbt nicht jedenFoo
‚sinit
Methoden , weil sie nicht eine der beiden Sonderfälle für Vererbungs erfüllt habeninit
Methoden von der übergeordneten Klasse.Wenn dies Objective-C wäre, würden wir dies
init
ohne Probleme erben , da Objective-C vollkommen glücklich ist, die Eigenschaften von Objekten nicht zu initialisieren (obwohl Sie als Entwickler damit nicht zufrieden sein sollten). In Swift reicht dies einfach nicht aus. Sie können keinen ungültigen Status haben, und das Erben von Superklassen-Initialisierern kann nur zu ungültigen Objektzuständen führen.quelle
Warum ist dieses Problem aufgetreten? Nun, die klare Tatsache ist, dass es immer wichtig war (dh in Objective-C, seit ich mit dem Programmieren von Cocoa in Mac OS X 10.0 begonnen habe), sich mit Initialisierern zu befassen, auf die Ihre Klasse nicht vorbereitet ist. In den Dokumenten war Ihre diesbezügliche Verantwortung immer klar. Aber wie viele von uns haben sich die Mühe gemacht, sie vollständig und schriftlich zu erfüllen? Wahrscheinlich keiner von uns! Und der Compiler hat sie nicht durchgesetzt; es war alles rein konventionell.
Beispiel: In meiner Objective-C-View-Controller-Unterklasse mit diesem festgelegten Initialisierer:
... ist es entscheidend, dass uns eine tatsächliche Sammlung von Medienobjekten übergeben wird: Die Instanz kann ohne eine solche einfach nicht entstehen. Aber ich habe keinen "Stopper" geschrieben, um zu verhindern, dass mich jemand
init
stattdessen mit bloßen Knochen initialisiert . Ich hätte einen schreiben sollen (eigentlich hätte ich eigentlich eine Implementierung desinitWithNibName:bundle:
geerbten designierten Initialisierers schreiben sollen ); aber ich war zu faul, um mich darum zu kümmern, weil ich "wusste", dass ich meine eigene Klasse niemals falsch auf diese Weise initialisieren würde. Dies hinterließ ein klaffendes Loch. In Objective-C, jemand kann nennen nackte Knocheninit
, so dass mein ivars nicht initialisiert, und wir sind auf den Bach ohne Paddel.Swift rettet mich in den meisten Fällen wunderbar vor mir selbst. Sobald ich diese App in Swift übersetzt hatte, verschwand das ganze Problem. Swift schafft effektiv einen Stopper für mich! Wenn
init(collection:MPMediaItemCollection)
der einzige festgelegte Initialisierer in meiner Klasse deklariert ist, kann ich nicht durch Aufrufen von Bare-Bones initialisiert werdeninit()
. Es ist ein Wunder!Was in Seed 5 passiert ist, ist lediglich, dass der Compiler erkannt hat, dass das Wunder im Fall von nicht funktioniert
init(coder:)
, weil theoretisch eine Instanz dieser Klasse von einer Feder stammen könnte und der Compiler dies nicht verhindern kann - und wann die Federlasteninit(coder:)
werden aufgerufen. Der Compiler lässt Sie den Stopper also explizit schreiben. Und ganz richtig auch.quelle
fatalError
beschriebenen Stopper ein . Machen Sie es einfach zu einem Benutzer-Code-Snippet und von nun an können Sie es einfach dort platzieren, wo es benötigt wird. Dauert eine halbe Sekunde.hinzufügen
quelle