In den vorhandenen Antworten fehlen zwei absolut wichtige Swift-spezifische Informationen, die meiner Meinung nach dazu beitragen, dies vollständig zu klären.
- Wenn ein Protokoll einen Initialisierer als erforderliche Methode angibt, muss dieser Initialisierer mit dem
required
Schlüsselwort Swift markiert werden .
- Swift hat spezielle Vererbungsregeln für
init
Methoden.
Das tl; dr ist das:
Wenn Sie Initialisierer implementieren, erben Sie keinen der von der Oberklasse festgelegten Initialisierer mehr.
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:
Im Gegensatz zu Unterklassen in Objective-C erben Swift-Unterklassen standardmäßig nicht ihre Superklassen-Initialisierer. Der Ansatz von Swift verhindert eine Situation, in der ein einfacher Initialisierer aus einer Oberklasse von einer spezialisierteren Unterklasse geerbt und zum Erstellen einer neuen Instanz der Unterklasse verwendet wird, die nicht vollständig oder korrekt initialisiert ist.
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 1
Wenn Ihre Unterklasse keine festgelegten Initialisierer definiert, erbt sie automatisch alle von der Oberklasse festgelegten Initialisierer.
Regel 2
Wenn Ihre Unterklasse eine Implementierung aller von der Oberklasse festgelegten Initialisierer bereitstellt - entweder durch Erben gemäß Regel 1 oder durch Bereitstellen einer benutzerdefinierten Implementierung als Teil ihrer Definition -, erbt sie automatisch alle Komfortklassen-Initialisierer der Oberklasse.
Regel 2 ist nicht besonders relevant für dieses Gespräch , weil SKSpriteNode
‚s init(coder: NSCoder)
ist unwahrscheinlich , dass eine bequeme Methode sein.
Ihre InfoBar
Klasse hat also den required
Initialisierer bis zu dem Punkt geerbt, den Sie hinzugefügt haben init(team: Team, size: CGSize)
.
Wenn Sie diese nicht zur Verfügung gestellt haben , waren init
Verfahren und stattdessen auf Ihre InfoBar
's hinzugefügt Eigenschaften optional oder versehen sie mit Standardwerten, dann würden Sie noch vererbt worden SKSpriteNode
ist init(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:
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")
Welches den folgenden Fehler darstellt:
Fehlendes Argument für Parameter 'bar' im Aufruf.
Wenn dies Objective-C wäre, hätte es kein Problem zu erben. Wenn wir a Bar
mit initWithFoo:
in Objective-C initialisieren self.bar
würden , wäre die Eigenschaft einfach nil
. 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. Es self.bar
ist nicht optional und kann nicht sein nil
.
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:
class Bar: Foo {
var bar: String
}
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 :
Problem: Die Klasse 'Bar' hat keine Initialisierer
Fix-It: Die gespeicherte Eigenschaftsleiste ohne Initialisierer verhindert synthetisierte Initialisierer
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 es required
?
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 spezielle init
Methoden erfordert (z. B. NSCoding
), dass die Klasse diese init
Methoden als markiert required
.
Betrachten Sie dieses Beispiel:
protocol InitProtocol {
init(foo: Int)
}
class ConformingClass: InitProtocol {
var foo: Int
init(foo: Int) {
self.foo = foo
}
}
Dies wird nicht kompiliert. Es wird die folgende Warnung generiert:
Problem: Die Initialisierungsanforderung 'init (foo :)' kann nur von einem 'erforderlichen' Initialisierer in der nicht endgültigen Klasse 'ConformingClass' erfüllt werden.
Fix-It: Einfügen erforderlich
Ich soll den init(foo: Int)
erforderlichen Initialisierer erstellen. Ich könnte es auch glücklich machen, indem ich die Klasse mache final
(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 anpasse InitProtocol
. 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 der init
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 beheben init(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 zu required
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-Cs instanceType
). Dazu muss ich jedoch eine required
Initialisierungsmethode verwenden.
class Box {
var size: CGSize
init(size: CGSize) {
self.size = size
}
class func factory() -> Self {
return self.init(size: CGSizeZero)
}
}
Dies erzeugt den Fehler:
Das Konstruieren eines Objekts vom Klassentyp 'Self' mit einem Metatypwert muss einen 'erforderlichen' Initialisierer verwenden
Es ist im Grunde das gleiche Problem. Wenn wir eine Unterklasse bilden Box
, erben unsere Unterklassen die Klassenmethode factory
. Also konnten wir anrufen SubclassedBox.factory()
. Ohne das required
Schlüsselwort in der init(size:)
Methode wird Box
jedoch nicht garantiert, self.init(size:)
dass factory
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 eine required
solche Methode implementiert, haben wir eine Initialisierungsmethode und stoßen auf genau dieselben Probleme, auf die Sie hier gestoßen sind mit dem NSCoding
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 die required
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):
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)
Dies kann nicht kompiliert werden.
Die Fehlermeldung ist etwas irreführend:
Zusätzliches Argument 'b' im Aufruf
Aber der Punkt ist, Bar
erbt nicht jeden Foo
‚s init
Methoden , weil sie nicht eine der beiden Sonderfälle für Vererbungs erfüllt haben init
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.
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.