Was genau ist Init Coder aDecoder?

122

Ich lerne die iOS-Entwicklung aus einem Online-Kurs und jedes Mal, wenn ich eine benutzerdefinierte Ansicht erstelle (benutzerdefinierte Tabellenansichtszelle, Sammlungsansichtszelle usw.), implementiert der Kursleiter diesen Initialisierer:

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

Warum genau muss ich das immer nennen? Was tut es? Kann ich Eigenschaften in den Init einfügen?

JasonP
quelle
5
Diese Antwort wird Ihnen helfen stackoverflow.com/questions/24036393/… Vielen Dank
Seungyoun Yi
2
Wenn Sie ein implementiertes Objekt in Unterklassen unterteilen, NSCodingmüssen Sie diesen Initialisierer implementieren, da er für implementierte Klassen erforderlich ist NSCoding. Sie müssen mindestens die Superclass-Init-Methode aufrufen. Wenn das NSCodercodierte Eigenschaften für Ihre Klasse enthält, können Sie diese Methode verwenden, um diese
wiederherzustellen
1
Außerdem empfehle ich Ihnen, den Abschnitt über die Objektinitialisierung im offiziellen Swift-Buch von Apple zu lesen.
Nicolas Miari

Antworten:

120

Ich beginne diese Antwort aus der entgegengesetzten Richtung: Was ist, wenn Sie den Status Ihrer Ansicht auf der Festplatte speichern möchten? Dies wird als Serialisierung bezeichnet . Das Gegenteil ist die Deserialisierung , bei der der Status des Objekts von der Festplatte wiederhergestellt wird.

Das NSCodingProtokoll definiert zwei Methoden zum Serialisieren und Deserialisieren von Objekten:

encodeWithCoder(_ aCoder: NSCoder) {
    // Serialize your object here
}

init(coder aDecoder: NSCoder) {
    // Deserialize your object here
}

Warum wird es in Ihrer benutzerdefinierten Klasse benötigt? Die Antwort lautet Interface Builder. Wenn Sie ein Objekt auf ein Storyboard ziehen und konfigurieren, serialisiert Interface Builder den Status dieses Objekts auf der Festplatte und deserialisiert es dann, wenn das Storyboard auf dem Bildschirm angezeigt wird. Sie müssen Interface Builder mitteilen, wie dies zu tun ist. Wenn Sie Ihrer Unterklasse keine neuen Eigenschaften hinzufügen, können Sie zumindest die Oberklasse einfach bitten, das Ein- und Auspacken für Sie durchzuführen, daher der super.init(coder: aDecoder)Aufruf. Wenn Ihre Unterklasse komplexer ist, müssen Sie Ihren eigenen Serialisierungs- und Deserialisierungscode für die Unterklasse hinzufügen.

Dies steht im Gegensatz zum Ansatz von Visual Studio, bei dem Code in eine versteckte Datei geschrieben wird, um das Objekt zur Laufzeit zu erstellen.

Code anders
quelle
Warum nicht alles in awakeFromNib stecken und die Verwendung vergessen init(coder aCoder : NSCoder)?
Honig
@Honey - mit einem Wort, "manchmal kann man das nicht machen". Sie können in der Regel aber nicht immer.
Fattie
@Fattie sind die Details, es nicht zu komplex oder unnötig zu wissen? Wenn nicht, macht es Ihnen etwas aus zu erklären?
Honey
9
@Honey Wenn Sie Ihr Objekt in Interface Builder konfigurieren möchten, awakeFromNibfunktioniert dies nicht. awakeFromNibwird zur Laufzeit aufgerufen . Alles, was Sie in Interface Builder tun, ist während der Entwurfszeit . Das, was Sie in der Entwurfszeit getan haben, zur Laufzeit zu übertragen, ist encodeWithCoder(Speichern) und init(coder:)(Laden)
Code Different
3
@Honey Wenn Sie Interface Builder nicht zum Konfigurieren Ihrer benutzerdefinierten Klasse verwenden (dh programmgesteuert mit Code ausführen), können Sie dies in awakeFromNiboderinitWIthFrame
Code Different
27

Die Anforderung, diesen Initialisierer zu implementieren, ist eine Folge von zwei Dingen:

  1. Das Liskov-Substitutionsprinzip . Wenn S eine Unterklasse von T ist (z. B. MyViewControllereine Unterklasse von ViewController), müssen S-Objekte (Instanzen von MyViewController) dort eingesetzt werden können, wo T-Objekte (Instanzen von ViewController) erwartet werden.

  2. Initialisierer werden in Swift nicht vererbt, wenn Initialisierer in der Unterklasse explizit definiert sind. Wenn ein Initialisierer explizit bereitgestellt wird, müssen alle anderen explizit bereitgestellt werden (der dann einfach aufgerufen werden kann super.init(...)). Siehe diese Frage zur Begründung. Es ist in Java, gilt aber immer noch.

Nach Punkt 1 sollte ViewControllerdie MyViewControllerUnterklasse alles können , was das Original kann . Eine solche Sache ist, von einer gegebenen initialisiert werden zu können NSCoder. Ab Punkt 2 MyViewControllererbt Ihre Unterklasse diese Fähigkeit nicht automatisch. Daher müssen Sie den Initialisierer, der diese Anforderung erfüllt, manuell angeben. In diesem Fall müssen Sie nur bis zur Oberklasse delegieren, damit diese das tut, was sie normalerweise tun würde.

Alexander - Monica wieder einsetzen
quelle
1
Es ist durchaus sinnvoll, dass Konstruktoren nicht vererbt werden: Wenn Sie eine Instanz der abgeleiteten Klasse mit dem (geerbten) Initialisierer der Basisklasse initialisieren, werden die nicht vererbten Eigenschaften, die von der abgeleiteten Klasse neu definiert ("hinzugefügt") wurden, niemals initialisiert werden.
Nicolas Miari
3
Tatsächlich werden Initialisierer in Swift vererbt, da Sie in Ihrer Unterklasse keine eigenen Initialisiererimplementierungen bereitstellen. Wenn Ihre neu definierten nicht vererbten Eigenschaften Standardwerte haben, können Sie keine Initialisierer in Ihre Unterklasse schreiben und einfach alle Initialisierer Ihrer Oberklasse erben. Siehe hier
TheBaj