ProgrammierungiOS/Swift Entwickler

Was ist Initializer Delegation (Delegierung der Initialisierung) in Swift, wie funktionieren die Delegierungsregeln zwischen designated und convenience Initializer und wie wirkt sich das auf die Vererbung aus?

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort.

Initializer Delegation ist ein in Swift für Klassen eingebautes System zur Delegierung der Initialisierung. In Swift gibt es historisch gesehen einen Unterschied zwischen designated initializer (der Hauptinitialisierer einer Klasse, der für die vollständige Initialisierung aller Eigenschaften verantwortlich ist) und convenience initializer (ein Hilfsinitialisierer, der das Erstellen einer Instanz mit verschiedenen Parameter-Sets erleichtert).

Problem: Wenn die Initialisierer zwischen Klassen und ihren Nachkommen chaotisch ausgeführt werden, kann es passieren, dass die Basisklasse nicht vollständig initialisiert wird oder die Initialisierung mehr als einmal durchgeführt wird, was zu einem ungültigen Objekt führt.

Die Lösung ist eine strenge Regel:

  1. Designated Initializer ruft immer den designated Initializer der Superklasse auf.
  2. Convenience Initializer ruft immer einen anderen Initializer aus der gleichen Klasse auf (designated oder einen anderen convenience).
  3. Convenience kann die Superklasse nicht direkt initiieren.

Beispielcode:

class Vehicle { var wheels: Int // Designated initializer init(wheels: Int) { self.wheels = wheels } // Convenience initializer convenience init() { self.init(wheels: 4) } } class Car: Vehicle { var color: String // Designated initializer init(wheels: Int, color: String) { self.color = color super.init(wheels: wheels) } // Convenience initializer convenience init(color: String) { self.init(wheels: 4, color: color) } }

Hauptmerkmale:

  • Minimaler Code-Duplikationsaufwand beim Erstellen von Instanzen mit unterschiedlichen Parameter-Sets.
  • Unterstützung der sicheren Vererbung von Klassen.
  • Garantierte korrekte Initialisierung aller Eigenschaften des Objekts.

Trickfragen.

Frage 1: Kann der convenience Initializer den Initializer der Superklasse direkt über super.init aufrufen?

Nein, der convenience Initializer delegiert immer die Initialisierung an einen anderen Initializer der aktuellen Klasse, der anschließend den designated Initializer der Superklasse aufrufen kann.

Frage 2: Was passiert, wenn der required Initializer nicht im Unterklasse implementiert wird?

Wenn die Superklasse einen required Initializer hat, muss dieser unbedingt in jeder Unterklasse (unter Verwendung von required) überschrieben werden, andernfalls gibt der Compiler einen Fehler aus.

Frage 3: Was ist der Unterschied zwischen convenience init und convenience required init?

convenience required init ist erforderlich, wenn der spezifische convenience Initializer im Superklasse als required deklariert wird, um die Unterstützung der Initialisierung in Vererbungshierarchien zu gewährleisten.

Typische Fehler und Anti-Pattern

  • Falsche Aufrufkette von convenience/init und Verletzung der Initialisierung von Eigenschaften.
  • Vergessen, required Initializer in der Unterklasse zu implementieren.
  • Aufruf von super.init aus convenience, was ungültig ist.

Beispiel aus dem Leben

Negativer Fall

Ein Entwickler hat super.init aus dem convenience Initializer aufgerufen. Der Code wurde nur aufgrund des Fehlens bestimmter Einschränkungen kompiliert, aber zur Laufzeit trat ein Fehler auf: nicht alle Eigenschaften des Objekts waren initialisiert.

Vorteile:

  • Der Code schien für Anfänger verständlich, da er ähnlichen Implementierungen in anderen Sprachen ähnelte.

Nachteile:

  • Es traten Bugs bei der Vererbung auf – untergeordnete Klassen wurden nicht korrekt initialisiert, die Anwendung stürzte ab.
  • Ein Refactoring war erforderlich.

Positiver Fall

Es wurden klar strukturierte designated und convenience Initializer mit explizitem Aufruf voneinander verwendet. Die Logik wurde streng nach den Regeln von Swift aufgerufen, daher war die Initialisierung immer transparent und korrekt.

Vorteile:

  • Die Klassenhierarchie lässt sich leicht erweitern und pflegen.
  • Doppelt enthaltene Codes wurden ausgeschlossen.
  • Das Testen wurde vereinfacht.

Nachteile:

  • Eine sorgfältige Dokumentation der Vererbung von Initializern war in großen Hierarchien erforderlich.