Erkundung von Swift-Initialisierern / Objektpartnern

In diesem Artikel werde ich einige der wichtigsten Details von Swift-Initialisierern untersuchen. Warum ein so spannendes Thema aufgreifen? Hauptsächlich, weil ich gelegentlich durch Kompilierungsfehler verwirrt war, wenn ich versuchte, eine Unterklasse von UIView zu erstellen, oder wenn ich versuchte, eine schnelle Version einer alten Objective-C-Klasse zu erstellen. Also beschloss ich, tiefer zu graben, um die Nuancen von Swift-Initialisierern besser zu verstehen. Da mein Hintergrund / meine Erfahrung hauptsächlich mit Groovy, Java und Javascript zu tun hat, werde ich einige der Initialisierungsmechanismen von Swift mit denen von Java & Groovy vergleichen. Ich ermutige Sie, einen Spielplatz in XCode zu starten und selbst mit den Codebeispielen zu spielen. Ich empfehle Ihnen auch, den Initialisierungsabschnitt des Swift 2.1 Language Guide zu lesen, der die primäre Informationsquelle für diesen Artikel darstellt. Schließlich werde ich mich eher auf Referenztypen (Klassen) als auf Werttypen (Strukturen) konzentrieren.

Was ist ein Initialisierer?

Ein Initialisierer ist ein spezieller Methodentyp in einer Swift-Struktur, Klasse oder Enum, der dafür verantwortlich ist, dass eine neu erstellte Instanz der Struktur, Klasse oder Enum vollständig initialisiert wird, bevor sie verwendet werden kann. Sie spielen die gleiche Rolle wie die eines „Konstruktors“ in Java und Groovy. Wenn Sie mit Objective-C vertraut sind, sollten Sie beachten, dass sich Swift-Initialisierer von Objective-C-Initialisierern dadurch unterscheiden, dass sie keinen Wert zurückgeben.

Unterklassen erben im Allgemeinen keine Initialisierer

Eines der ersten Dinge, die Sie beachten sollten, ist, dass „Swift-Unterklassen ihre Oberklasseninitialisierer standardmäßig nicht erben“, so der Sprachführer. (Im Handbuch wird erläutert, dass es Szenarien gibt, in denen Superklasseninitialisierer automatisch vererbt werden. Wir werden diese außergewöhnlichen Szenarien später behandeln). Dies steht im Einklang mit der Funktionsweise von Java (und im weiteren Sinne von Groovy). Betrachten Sie Folgendes:

Genau wie bei Java & Groovy ist es sinnvoll, dass dies nicht zulässig ist (obwohl es, wie bei den meisten Dingen, zu diesem Punkt einige Argumente geben kann. Siehe diesen StackOverflow-Beitrag ) . Wenn dies zulässig wäre, würde die Initialisierung der Eigenschaft „instrument“ von Musician umgangen, wodurch Ihre Musician-Instanz möglicherweise in einem ungültigen Zustand verbleibt. Mit Groovy würde ich mich jedoch normalerweise nicht darum kümmern, Initialisierer (dh Konstruktoren) zu schreiben. Vielmehr würde ich einfach den Kartenkonstruktor verwenden, den Groovy implizit bereitstellt, sodass Sie frei auswählen können, welche Eigenschaften Sie beim Erstellen festlegen möchten. Zum Beispiel ist das Folgende vollkommen gültiger Groovy-Code:

Beachten Sie, dass Sie jede Eigenschaft einschließen können, einschließlich derjenigen, die von Superklassen bereitgestellt werden, aber Sie müssen nicht alle (oder einige) von ihnen angeben, und sie müssen nicht in einer bestimmten Reihenfolge angegeben werden. Diese Art von ultraflexiblem Initialisierer wird von Swift nicht bereitgestellt. Das Nächste in Swift sind die automatisch bereitgestellten memberweisen Initialisierer für Strukturen. Die Reihenfolge der Argumente in einem memberweisen Initialisierer ist jedoch von Bedeutung, obwohl sie benannt sind, und hängt von der Reihenfolge ab, in der sie definiert sind:
Zurück zu den Klassen – Groovys Philosophie in Bezug auf die Gültigkeit von Objekten nach der Konstruktion unterscheidet sich eindeutig stark von der von Swift. Dies ist nur eine von vielen Arten, in denen sich Groovy von Swift unterscheidet.

Designated vs Convenience Initializers

Bevor wir zu weit in das Kaninchenloch kommen, sollten wir ein wichtiges Konzept klären: In Swift wird ein Initialisierer entweder als designierter oder als Convenience-Initialisierer kategorisiert. Ich werde versuchen, den Unterschied sowohl konzeptionell als auch syntaktisch zu erklären. Jede Klasse muss mindestens einen bestimmten Initialisierer haben, kann aber mehrere haben („bezeichnet“ bedeutet nicht „einzeln“). Ein designierter Initialisierer wird als primärer Initialisierer betrachtet. Sie sind die Head-Honchos. Sie sind letztendlich dafür verantwortlich, dass alle Eigenschaften initialisiert werden. Aufgrund dieser Verantwortung kann es manchmal mühsam sein, einen bestimmten Initialisierer ständig zu verwenden, da möglicherweise mehrere Argumente erforderlich sind. Stellen Sie sich vor, Sie arbeiten mit einer Klasse mit mehreren Eigenschaften, und Sie müssen mehrere Instanzen dieser Klasse erstellen, die bis auf eine oder zwei Eigenschaften nahezu identisch sind. (Aus Gründen der Argumentation nehmen wir auch an, dass es keine vernünftigen Standardwerte gibt, die Eigenschaften zugewiesen werden könnten, wenn sie deklariert werden). Nehmen wir zum Beispiel an, unsere Person-Klasse hatte auch eatsFood- und enjoysMusic-Eigenschaften. Natürlich würden diese beiden Dinge die meiste Zeit wahr sein, aber man weiß nie. Werfen Sie einen Blick darauf:

Jetzt hat unsere Person-Klasse vier Eigenschaften, die festgelegt werden müssen, und wir haben einen bestimmten Initialisierer, der die Arbeit erledigen kann. Der angegebene Initialisierer ist der erste, der 4 Argumente benötigt. Meistens haben diese letzten beiden Argumente den Wert „true“. Es wäre schmerzhaft, sie jedes Mal angeben zu müssen, wenn wir eine typische Person erstellen möchten. Hier kommen die letzten beiden Initialisierer ins Spiel, die mit dem Convenience-Modifikator markiert sind. Dieses Muster sollte einem Java-Entwickler bekannt vorkommen. Wenn Sie einen Konstruktor haben, der mehr Argumente benötigt, als Sie wirklich ständig verarbeiten müssen, können Sie vereinfachte Konstruktoren schreiben, die eine Teilmenge dieser Argumente verwenden und vernünftige Standardwerte für die anderen bereitstellen. Die Komfortinitialisierer müssen entweder an einen anderen, möglicherweise weniger praktischen Komfortinitialisierer oder an einen bestimmten Initialisierer delegieren. Letztendlich muss ein designierter Initialisierer einbezogen werden. Wenn dies eine Unterklasse ist, muss der angegebene Initialisierer einen angegebenen Initialisierer aus seiner unmittelbaren Oberklasse aufrufen.

Ein reales Beispiel für die Verwendung des Convenience-Modifikators stammt aus der UIBezierPath-Klasse von UIKit. Ich bin sicher, Sie können sich vorstellen, dass es mehrere Möglichkeiten gibt, einen Pfad anzugeben. Daher bietet UIBezierPath mehrere Komfortinitialisierer:

public convenience init(rect: CGRect)
public convenience init(ovalInRect rect: CGRect)
public convenience init(roundedRect rect: CGRect, cornerRadius: CGFloat)
public convenience init(roundedRect rect: CGRect, byRoundingCorners corners: uictcorner, cornerRadii : CGSize)
public convenience init(arcCenter center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool)
public( init(CGPath: CGPath)

Zuvor sagte ich, dass eine Klasse möglicherweise mehrere festgelegte Initialisierer hat. Also, wie sieht das aus? Ein wichtiger, vom Compiler erzwungener Unterschied zwischen designierten Initialisierern und Convenience-Initialisierern besteht darin, dass designierte Initialisierer möglicherweise nicht an einen anderen Initialisierer in derselben Klasse delegieren (sondern an einen designierten Initialisierer in seiner unmittelbaren Oberklasse delegieren müssen). Schauen Sie sich den zweiten Initialisierer von Person an, der ein einzelnes Argument namens „unDead“ verwendet. Da dieser Initialisierer nicht mit dem Convenience-Modifikator markiert ist, behandelt Swift ihn als ausgewiesenen Initialisierer. Daher kann es nicht persönlich an einen anderen Initialisierer delegiert werden. Versuchen Sie, die ersten vier Zeilen zu kommentieren und die letzte Zeile zu kommentieren. Der Compiler wird sich beschweren, und XCode sollte versuchen, Ihnen zu helfen, indem er vorschlägt, dass Sie das Problem beheben, indem Sie den Convenience-Modifikator hinzufügen.

Betrachten Sie nun die Musiker-Unterklasse von Person. Es hat einen einzigen Initialisierer und muss daher ein bestimmter Initialisierer sein. Als solches muss es einen bestimmten Initialisierer der unmittelbaren Oberklasse Person aufrufen. Denken Sie daran: Während designierte Initialisierer nicht an einen anderen Initialisierer derselben Klasse delegieren können, müssen Convenience-Initialisierer dies tun. Außerdem muss ein designierter Initialisierer einen designierten Initialisierer seiner unmittelbaren Oberklasse aufrufen. Weitere Informationen (und hübsche Grafiken) finden Sie im Sprachführer.

Initialisierungsphasen

Wie der Swift Language Guide erklärt, gibt es zwei Initialisierungsphasen. Die Phasen werden durch den Aufruf des als Superklasse bezeichneten Initialisierers abgegrenzt. Phase 1 ist vor dem Aufruf des als Superklasse bezeichneten Initialisierers, Phase 2 ist danach. Eine Unterklasse muss alle ihre EIGENEN Eigenschaften in Phase 1 initialisieren und darf bis Phase 2 KEINE von der Oberklasse definierten Eigenschaften festlegen.
Hier ist ein Codebeispiel, das aus einem im Sprachführer bereitgestellten Beispiel angepasst wurde und zeigt, dass Sie die EIGENEN Eigenschaften einer Unterklasse initialisieren müssen, bevor Sie den als Initialisierer bezeichneten Oberklasseninitialisierer aufrufen. Sie können erst nach dem Aufrufen des von der Oberklasse angegebenen Initialisierers auf die von der Oberklasse bereitgestellten Eigenschaften zugreifen. Schließlich können Sie die gespeicherten Eigenschaften nicht ändern, nachdem der von der Oberklasse angegebene Initialisierer aufgerufen wurde.

Überschreiben eines Initialisierers

Nun, da wir davon überzeugt sind, dass Unterklassen im Allgemeinen keine Initialisierer erben, und wir uns über die Bedeutung und den Unterschied zwischen designierten und Convenience-Initialisierern im Klaren sind, wollen wir überlegen, was passiert, wenn eine Unterklasse einen Initialisierer aus ihrer unmittelbaren Oberklasse überschreiben soll. Es gibt vier Szenarien, die ich behandeln möchte, da es zwei Arten von Initialisierern gibt. Nehmen wir sie also nacheinander mit einfachen Codebeispielen für jeden Fall:

Ein designierter Initialisierer, der mit einem Superklasse-designierten Initialisierer übereinstimmt
Dies ist ein typisches Szenario. Wenn Sie dies tun, müssen Sie den Override-Modifikator anwenden. Beachten Sie, dass dieses Szenario auch dann gilt, wenn Sie einen automatisch bereitgestellten Standardinitialisierer „überschreiben“ (dh wenn die Superklasse keinen expliziten Initialisierer definiert. In diesem Fall stellt Swift implizit eine zur Verfügung. Java-Entwickler sollten mit diesem Verhalten vertraut sein). Dieser automatisch bereitgestellte Standardinitialisierer ist immer ein festgelegter Initialisierer.
Ein designierter Initialisierer, der mit einem Convenience-Initialisierer der Oberklasse übereinstimmt
Nehmen wir nun an, Sie möchten Ihrer Unterklasse einen designierten Initialisierer hinzufügen, der zufällig mit einem Convenience-Initialisierer im übergeordneten Element übereinstimmt. Gemäß den im Sprachführer festgelegten Regeln für die Initialisierungsdelegierung muss der von Ihrer Unterklasse angegebene Initialisierer an einen bestimmten Initialisierer der unmittelbaren Oberklasse delegieren. Das heißt, Sie können nicht an den passenden Komfortinitialisierer des Elternteils delegieren. Nehmen wir aus Gründen der Argumentation auch an, dass die Unterklasse nicht zum Erben von Superklasseninitialisierern berechtigt ist. Da Sie dann niemals eine Instanz Ihrer Unterklasse erstellen könnten, indem Sie den Komfortinitialisierer der Oberklasse direkt aufrufen, ist dieser passende Komfortinitialisierer ohnehin nicht am Initialisierungsprozess beteiligt und könnte dies auch nie sein. Daher überschreiben Sie es nicht wirklich und der Override-Modifikator gilt nicht.

Ein Convenience-Initialisierer, der mit einem von einer Oberklasse bezeichneten Initialisierer übereinstimmt
Stellen Sie sich in diesem Szenario vor, Sie haben eine Unterklasse, die eigene Eigenschaften hinzufügt, deren Standardwert aus den Werten berechnet werden kann (aber nicht muss), die einer oder mehreren Eigenschaften der übergeordneten Klasse zugewiesen sind. Angenommen, Sie möchten auch nur einen bestimmten Initialisierer für Ihre Unterklasse haben. Sie können Ihrer Unterklasse einen Komfortinitialisierer hinzufügen, dessen Signatur mit der eines bestimmten Initialisierers der übergeordneten Klasse übereinstimmt. In diesem Fall würde Ihr neuer Initialisierer sowohl die Convenience- als auch die Override-Modifikatoren benötigen. Hier ist ein gültiges Codebeispiel, um diesen Fall zu veranschaulichen:
Ein Convenience-Initialisierer, der mit einem Convenience-Initialisierer der Oberklasse übereinstimmt
Wenn Sie Ihrer Unterklasse einen Convenience-Initialisierer hinzufügen möchten, der zufällig mit der Signatur eines Convenience-Initialisierers Ihrer Oberklasse übereinstimmt, fahren Sie einfach fort. Wie ich oben erklärt habe, können Sie die Initialisierer sowieso nicht wirklich überschreiben. Sie würden also den Convenience-Modifikator einschließen, aber den Override-Modifikator weglassen und ihn wie jeden anderen Convenience-Initialisierer behandeln.

Eine wichtige Erkenntnis aus diesem Abschnitt ist, dass der Override-Modifikator nur verwendet wird und verwendet werden muss, wenn Sie einen von der Oberklasse bezeichneten Initialisierer überschreiben. (Kleinere Klarstellung hier: Wenn Sie einen erforderlichen Initialisierer überschreiben, verwenden Sie den erforderlichen Modifikator anstelle des Überschreibungsmodifikators. Der erforderliche Modifikator impliziert den Override-Modifikator. Siehe Abschnitt Erforderliche Initialisierer unten).

Wenn Initialisierer vererbt werden

Nun zu den oben genannten Szenarien, in denen Superklasseninitialisierer vererbt werden. Wie im Swift Language Guide erläutert, erbt Ihre Unterklasse automatisch alle von ihrer Oberklasse bezeichneten Initialisierer, wenn sie bei der Deklaration Standardwerte für alle ihre eigenen Eigenschaften bereitstellt und keinen ihrer eigenen bezeichneten Initialisierer definiert. Wenn Ihre Unterklasse eine Implementierung aller von der Oberklasse bezeichneten Initialisierer bereitstellt, erbt sie automatisch alle von der Oberklasse bezeichneten Initialisierer. Dies steht im Einklang mit der Regel in Swift, dass die Initialisierung von Klassen (und Strukturen) gespeicherte Eigenschaften nicht in einem unbestimmten Zustand belassen darf.

Ich bin beim Experimentieren mit Convenience initializers, designated initializers und den Vererbungsregeln auf ein „interessantes“ Verhalten gestoßen. Ich fand, dass es möglich ist, einen Teufelskreis versehentlich einzurichten. Betrachten Sie das folgende Beispiel:

Die RecipeIngredient-Klasse überschreibt alle von der Food-Klasse bezeichneten Initialisierer und erbt daher automatisch alle von der Superklasse bezeichneten Initialisierer. Der Food Convenience-Initialisierer delegiert jedoch vernünftigerweise an seinen eigenen Initialisierer, der von der Unterklasse RecipeIngredient überschrieben wurde. Es wird also nicht die Food-Version dieses init-Initialisierers (name: String) aufgerufen, sondern die überschriebene Version in RecipeIngredient . Die überschriebene Version nutzt die Tatsache, dass die Unterklasse den Convenience–Initialisierer von Food geerbt hat, und da ist es – Sie haben einen Zyklus. Ich weiß nicht, ob dies als Programmierfehler oder als Compilerfehler angesehen werden würde (ich habe es als Fehler gemeldet: https://bugs.swift.org/browse/SR-512). Stellen Sie sich vor, dies ist eine Klasse von einer 3rd-Party, und Sie haben keinen Zugriff auf den Quellcode, so dass Sie nicht wissen, wie es tatsächlich implementiert ist. Sie würden (bis zur Laufzeit) nicht wissen, dass die Verwendung in der in diesem Beispiel gezeigten Weise Sie in einem Zyklus gefangen halten würde. Ich denke, es wäre besser, wenn der Compiler uns hier helfen würde.

Fehlerhafte Initialisierer

Stellen Sie sich vor, Sie haben eine Klasse mit bestimmten Invarianten entworfen und möchten diese Invarianten von dem Moment an erzwingen, in dem eine Instanz der Klasse erstellt wird. Vielleicht modellieren Sie beispielsweise eine Rechnung und möchten sicherstellen, dass der Betrag immer nicht negativ ist. Wenn Sie einen Initialisierer hinzugefügt haben, der ein amount Argument vom Typ Double , wie können Sie sicherstellen, dass Sie Ihre Invariante nicht verletzen? Eine Strategie besteht darin, einfach zu überprüfen, ob das Argument nicht negativ ist. Wenn ja, verwenden Sie es. Andernfalls ist der Standardwert 0. Zum Beispiel:

Dies würde funktionieren und kann akzeptabel sein, wenn Sie dokumentieren, was Ihr Initialisierer tut (insbesondere wenn Sie vorhaben, Ihre Klasse anderen Entwicklern zur Verfügung zu stellen). Aber es könnte Ihnen schwer fallen, diese Strategie zu verteidigen, da sie das Problem irgendwie unter den Teppich kehrt. Ein anderer Ansatz, der von Swift unterstützt wird, wäre, die Initialisierung fehlschlagen zu lassen. Das heißt, Sie würden Ihren Initialisierer fehlschlagen lassen.

Wie in diesem Artikel beschrieben, wurden fehlerhafte Initialisierer zu Swift hinzugefügt, um die Notwendigkeit von Factory-Methoden zu eliminieren oder zu reduzieren, „die zuvor die einzige Möglichkeit waren, Fehler zu melden“ während der Objektkonstruktion. Um einen Initialisierer fehlerhaft zu machen, fügen Sie einfach das ? oder ! zeichen nach dem Schlüsselwort init (dh init? oder init! ). Nachdem alle Eigenschaften festgelegt und alle anderen Delegierungsregeln erfüllt wurden, fügen Sie eine Logik hinzu, um zu überprüfen, ob die Argumente gültig sind. Wenn sie ungültig sind, lösen Sie einen Initialisierungsfehler mit return nil aus. Beachten Sie, dass dies nicht bedeutet, dass der Initialisierer jemals etwas zurückgibt. So könnte unsere Invoice-Klasse mit einem fehlerhaften Initialisierer aussehen:
Bemerken Sie etwas anderes darüber, wie wir das Ergebnis der Objekterstellung verwenden? Es ist, als würden wir es als optional behandeln, oder? Nun, genau das machen wir! Wenn wir einen fehlerhaften Initialisierer verwenden, erhalten wir entweder nil (wenn ein Initialisierungsfehler ausgelöst wurde) oder einen optionalen (Optional). Das heißt, wenn die Initialisierung erfolgreich war, erhalten wir ein Optionales Element, das die Rechnungsinstanz umschließt, an der wir interessiert sind. (Beachten Sie, dass Java ab Java 8 auch Optionen hat).

Fehlerhafte Initialisierer sind genau wie die anderen Arten von Initialisierern, die wir in Bezug auf Überschreiben und Delegieren, Designated vs Convenience usw. besprochen haben … Tatsächlich können Sie sogar einen fehlerhaften Initialisierer mit einem nicht verfügbaren Initialisierer überschreiben. Sie können jedoch einen nicht verfügbaren Initialisierer nicht durch einen fehlerhaften überschreiben.

Möglicherweise haben Sie fehlerhafte Initialisierer beim Umgang mit UIView oder UIViewController bemerkt, die beide einen fehlerhaften Initialisierer-Init bereitstellen?(coder aDecoder: NSCoder). Dieser Initialisierer wird aufgerufen, wenn Ihre Ansicht oder Ihr ViewController von einer Feder geladen wird. Es ist wichtig zu verstehen, wie fehlerhafte Initialisierer funktionieren. Ich empfehle Ihnen dringend, den Abschnitt Fehlerhafte Initialisierer des Swift Language Guide durchzulesen, um eine gründliche Erklärung zu erhalten.

Erforderliche Initialisierer

Der erforderliche Modifikator wird verwendet, um anzugeben, dass alle Unterklassen den betroffenen Initialisierer implementieren müssen. Auf den ersten Blick klingt das ziemlich einfach und unkompliziert. Es kann manchmal etwas verwirrend werden, wenn Sie nicht verstehen, wie die oben diskutierten Regeln zur Initialisierungsvererbung ins Spiel kommen. Wenn eine Unterklasse die Kriterien erfüllt, nach denen Superklasseninitialisierer vererbt werden, enthält die Gruppe der geerbten Initialisierer die als erforderlich gekennzeichneten. Daher erfüllt die Unterklasse implizit den durch den erforderlichen Modifikator auferlegten Vertrag. Es implementiert die erforderlichen Initialisierer, Sie sehen es einfach nicht im Quellcode.

Wenn eine Unterklasse eine explizite (dh nicht vererbte) Implementierung eines erforderlichen Initialisierers bereitstellt, überschreibt sie auch die Implementierung der Oberklasse. Der erforderliche Modifikator impliziert override , daher wird der Override-Modifikator nicht verwendet. Sie können es einschließen, wenn Sie möchten, aber dies wäre redundant und XCode wird Sie darüber nörgeln.

Der Swift-Sprachführer sagt nicht viel über den erforderlichen Modifikator aus, daher habe ich ein Codebeispiel (siehe unten) mit Kommentaren vorbereitet, um den Zweck zu erläutern und zu beschreiben, wie er funktioniert. Weitere Informationen finden Sie in diesem Artikel von Anthony Levings.

Sonderfall: Erweitern von UIView

Eines der Dinge, die mich dazu veranlassten, mich intensiv mit Swift-Initialisierern zu befassen, war mein Versuch, einen Weg zu finden, einen Satz bestimmter Initialisierer zu erstellen, ohne die Initialisierungslogik zu duplizieren. Zum Beispiel habe ich dieses UIView-Tutorial von Ray Wenderlich durchgearbeitet und dabei seinen Objective-C-Code in Swift konvertiert (Sie können sich meine Swift-Version hier ansehen). Wenn Sie sich dieses Tutorial ansehen, werden Sie feststellen, dass seine RateView-Unterklasse von UIView über eine baseInit-Methode verfügt, mit der beide angegebenen Initialisierer allgemeine Initialisierungsaufgaben ausführen. Das scheint mir ein guter Ansatz zu sein – Sie möchten dieses Zeug nicht in jedem dieser Initialisierer duplizieren. Ich wollte diese Technik in meiner schnellen Version von RateView neu erstellen. Ich fand es jedoch schwierig, da ein bestimmter Initialisierer nicht an einen anderen Initialisierer in derselben Klasse delegieren und Methoden seiner eigenen Klasse erst aufrufen kann, nachdem er an den Oberklasseninitialisierer delegiert wurde. Zu diesem Zeitpunkt ist es zu spät, konstante Eigenschaften festzulegen. Natürlich könnten Sie diese Einschränkung umgehen, indem Sie keine Konstanten verwenden, aber das ist keine gute Lösung. Daher dachte ich, es sei am besten, nur Standardwerte für die gespeicherten Eigenschaften anzugeben, in denen sie deklariert sind. Das ist immer noch die beste Lösung, die ich derzeit kenne. Ich habe jedoch eine alternative Technik herausgefunden, die Initialisierer verwendet.

Sehen Sie sich das folgende Beispiel an. Dies ist ein Ausschnitt aus RateViewWithConvenienceInits .swift , eine alternative Version meines Swift-Ports von RateView . Als Unterklasse von UIView, die bei der Deklaration keine Standardwerte für alle eigenen gespeicherten Eigenschaften bereitstellt, muss diese alternative Version von RateView mindestens eine explizite Implementierung der erforderlichen init von UIView bereitstellen?(coder aDecoder: NSCoder) Initialisierer. Wir werden auch eine explizite Implementierung von UIViews init(frame) bereitstellen wollen: CGRect) Initialisierer, um sicherzustellen, dass der Initialisierungsprozess konsistent ist. Wir möchten, dass unsere gespeicherten Eigenschaften auf die gleiche Weise eingerichtet werden, unabhängig davon, welcher Initialisierer verwendet wird.
Beachten Sie, dass ich den Komfortmodifikator zu den überschriebenen Versionen der Initialisierer von UIView hinzugefügt habe. Ich habe der Unterklasse auch einen fehlgeschlagenen designierten Initialisierer hinzugefügt, an den beide überschriebenen (Komfort-) Initialisierer delegieren. Dieser einzelne benannte Initialisierer kümmert sich um das Einrichten aller gespeicherten Eigenschaften (einschließlich Konstanten – ich musste nicht für alles auf var zurückgreifen). Es funktioniert, aber ich denke, es ist ziemlich kludgy. Ich würde es vorziehen, nur Standardwerte für meine gespeicherten Eigenschaften anzugeben, wo sie deklariert sind, aber es ist gut zu wissen, dass diese Option bei Bedarf vorhanden ist.

You might also like

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.