Swift From Scratch Initialisierung und Initialisierungsdelegierung

In der vorherigen Lektion von Swift From Scratch haben wir eine funktionale Aufgabenanwendung erstellt. Das Datenmodell könnte jedoch etwas Liebe gebrauchen. In dieser letzten Lektion werden wir das Datenmodell durch Implementierung einer benutzerdefinierten Modellklasse umgestalten.

1. Das Datenmodell

Das Datenmodell, das wir implementieren werden, umfasst zwei Klassen: a Aufgabe Klasse und a Machen Klasse, die von der erbt Aufgabe Klasse. Während wir diese Modellklassen erstellen und implementieren, werden wir die objektorientierte Programmierung in Swift weiter erforschen. In dieser Lektion betrachten wir die Initialisierung von Klasseninstanzen und die Rolle, die die Vererbung während der Initialisierung spielt.

Das Aufgabe Klasse

Beginnen wir mit der Implementierung des Aufgabe Klasse. Erstellen Sie eine neue Swift-Datei, indem Sie auswählen Neu> Datei… von Xcode's Datei Speisekarte. Wählen Swift File von dem iOS> Quelle Sektion. Benennen Sie die Datei Task.Schalten und schlagen Erstellen.

Die grundlegende Implementierung ist kurz und einfach. Das Aufgabe Klasse erbt von NSObject, definiert in der Stiftung Framework und hat eine variable Eigenschaft Name vom Typ String. Die Klasse definiert zwei Initialisierer, drin() und init (Name :). Es gibt ein paar Details, die Sie vielleicht in die Irre führen können, also lassen Sie mich erklären, was passiert.

Foundation-Klasse importieren Task: NSObject Variablenname: String-Bequemlichkeit überschreiben Init () self.init (Name: "Neue Task") Init (Name: String) Self.name = Name

Weil der drin() Methode ist auch in definiert NSObject Klasse müssen wir dem Initialisierer das vorangestellt überschreiben Stichwort. Wir haben oben in dieser Serie übergeordnete Methoden behandelt. In dem drin() Methode rufen wir die init (Name :) Methode, vorbei "Neue Aufgabe" als Wert für die Name Parameter.

Das init (Name :) method ist ein weiterer Initialisierer, der einen einzelnen Parameter akzeptiert Name vom Typ String. In diesem Initialisierer wird der Wert von Name Parameter ist der zugeordnet Name Eigentum. Das ist leicht zu verstehen. Recht?

Designierte und komfortable Initialisierer

Was ist mit dem Bequemlichkeit Schlüsselwort vor dem drin() Methode? Klassen können zwei Arten von Initialisierern haben, vorgesehen Initialisierer und Bequemlichkeit Initialisierer. Bequemlichkeitsinitialisierer sind mit dem vorangestellten Bequemlichkeit Stichwort, was das impliziert init (Name :) ist ein ausgewiesener Initialisierer. Warum das? Was ist der Unterschied zwischen benannten und praktischen Initialisierern??

Designierte Initialisierer Initialisieren Sie eine Instanz einer Klasse vollständig, was bedeutet, dass jede Eigenschaft der Instanz nach der Initialisierung einen Anfangswert hat. Mit Blick auf die Aufgabe Klasse sehen wir zum Beispiel, dass die Name Eigenschaft wird mit dem Wert von gesetzt Name Parameter des init (Name :) Initialisierer. Das Ergebnis nach der Initialisierung ist vollständig initialisiert Aufgabe Beispiel.

Komfort-Initialisierer, Verlassen Sie sich jedoch auf einen bestimmten Initialisierer, um eine vollständig initialisierte Instanz der Klasse zu erstellen. Deshalb ist das drin() Initialisierung der Aufgabe Klasse ruft die init (Name :) Initialisierung in seiner Implementierung. Dies wird als bezeichnet Initialisierungsdelegation. Das drin() Der Initialisierer delegiert die Initialisierung an einen bestimmten Initialisierer, um eine vollständig initialisierte Instanz des zu erstellen Aufgabe Klasse.

Komfort-Initialisierer sind optional. Nicht jede Klasse verfügt über einen praktischen Initialisierer. Bestimmte Initialisierer sind erforderlich, und eine Klasse muss mindestens einen bestimmten Initialisierer haben, um eine vollständig initialisierte Instanz von sich selbst zu erstellen.

Das NSCoding Protokoll

Die Umsetzung der Aufgabe Der Unterricht ist jedoch nicht abgeschlossen. Später in dieser Lektion schreiben wir ein Array von Machen Instanzen auf die Festplatte. Dies ist nur möglich, wenn Instanzen von Machen Klasse kann codiert und decodiert werden.

Keine Sorge, das ist keine Hexerei. Wir müssen nur das machen Aufgabe und Machen Klassen entsprechen der NSCoding Protokoll. Deshalb ist das Aufgabe Klasse erbt von der NSObject Klasse seit dem NSCoding Das Protokoll kann nur von Klassen implementiert werden, die direkt oder indirekt erben NSObject. Wie NSObject Klasse, die NSCoding Protokoll ist in der definiert Stiftung Rahmen.

Die Annahme eines Protokolls ist etwas, das wir bereits in dieser Serie behandelt haben, aber es gibt ein paar Probleme, auf die ich hinweisen möchte. Beginnen wir damit, dem Compiler zu sagen, dass das Aufgabe Klasse entspricht der NSCoding Protokoll.

Foundation-Klasse importieren Aufgabe: NSObject, NSCoding Variablenname: String…

Als nächstes müssen wir die beiden in der deklarierten Methoden implementieren NSCoding Protokoll, init? (Coder :) und kodieren (mit :). Die Implementierung ist unkompliziert, wenn Sie mit der NSCoding Protokoll.

Foundation-Klasse importieren Aufgabe: NSObject, NSCoding Variablenname: Zeichenfolge @objc erforderlich init? (Coder aDecoder: NSCoder) name = aDecoder.decodeObject (forKey: "name") as! String @objc func encode (mit aCoder: NSCoder) aCoder.encode (name, forKey: "name") Bequemlichkeit überschreiben init () self.init (name: "New Task") init (name: String) self.name = name

Das init? (Coder :) Initialisierer ist ein bestimmter Initialisierer, der a initialisiert Aufgabe Beispiel. Obwohl wir das implementieren init? (Coder :) Methode zur Anpassung an die NSCoding Protokoll, müssen Sie diese Methode nie direkt aufrufen. Gleiches gilt für kodieren (mit :), die eine Instanz des codiert Aufgabe Klasse.

Das erforderlich Schlüsselwort vor dem init? (Coder :) Methode zeigt an, dass jede Unterklasse der Aufgabe Klasse muss diese Methode implementieren. Das erforderlich Das Schlüsselwort gilt nur für Initialisierer. Aus diesem Grund müssen wir es nicht dem hinzufügen kodieren (mit :) Methode.

Bevor wir weitermachen, müssen wir über das sprechen @objc Attribut. Weil der NSCoding Protokoll ist ein Objective-C-Protokoll, Protokollkonformität kann nur durch Hinzufügen von geprüft werden @objc Attribut. In Swift gibt es keine Protokollkonformität oder optionale Protokollmethoden. Mit anderen Worten, wenn eine Klasse einem bestimmten Protokoll entspricht, überprüft der Compiler und erwartet, dass jede Methode des Protokolls implementiert ist.

Das Machen Klasse

Mit dem Aufgabe Klasse implementiert, ist es Zeit, die implementieren Machen Klasse. Erstellen Sie eine neue Swift-Datei und benennen Sie sie ToDo.swift. Schauen wir uns die Implementierung von an Machen Klasse.

Foundation-Klasse importieren ToDo: Task var done: Bool @objc erfordert init? (Coder aDecoder: NSCoder) self.done = aDecoder.decodeBool (forKey: "done") encode (mit aCoder: NSCoder) aCoder.encode (done, forKey: "done") super.encode (mit: aCoder) init (name: String, done: Bool) self.done = done super.init (name : Name)  

Das Machen Klasse erbt von der Aufgabe Klasse und deklariert eine variable Eigenschaft erledigt vom Typ Bool. Neben den beiden erforderlichen Methoden des NSCoding Protokoll, das es von der erbt Aufgabe Klasse deklariert sie auch einen bestimmten Initialisierer, init (name: done :).

Wie in Objective-C wird der Super Schlüsselwort bezieht sich auf die Oberklasse, die Aufgabe Klasse in diesem Beispiel. Es gibt ein wichtiges Detail, das Aufmerksamkeit verdient. Bevor Sie das aufrufen init (Name :) Methode in der Oberklasse, jede von der deklarierte Eigenschaft Machen Klasse muss initialisiert werden. Mit anderen Worten vor dem Machen Klasse delegiert die Initialisierung an ihre Superklasse, jede durch die Machen Klasse muss einen gültigen Anfangswert haben. Sie können dies überprüfen, indem Sie die Reihenfolge der Anweisungen ändern und den angezeigten Fehler überprüfen.

Gleiches gilt für die init? (Coder :) Methode. Wir initialisieren zuerst das erledigt Eigenschaft vor dem Aufruf init? (Coder :) in der Oberklasse.

Initialisierer und Vererbung

Beim Umgang mit Vererbung und Initialisierung sind einige Regeln zu beachten. Die Regel für bestimmte Initialisierer ist einfach.

  • Ein ausgewiesener Initialisierer muss einen bestimmten Initialisierer aus seiner Superklasse aufrufen. In dem Machen Klasse zum Beispiel die init? (Coder :) Methode ruft die init? (Coder :) Methode seiner Oberklasse. Dies wird auch als bezeichnet Delegieren auf.

Die Regeln für Komfort-Initialisierer sind etwas komplexer. Es gibt zwei Regeln, die zu beachten sind.

  • Ein praktischer Initialisierer muss immer einen anderen Initialisierer der Klasse aufrufen, in der er definiert ist Aufgabe Klasse zum Beispiel die drin() method ist ein praktischer Initialisierer und delegiert die Initialisierung an einen anderen Initialisierer, init (Name :) im Beispiel. Dies ist bekannt als Delegieren über.
  • Auch wenn ein Initialisierungsprogramm für den Komfort nicht die Initialisierung an einen bestimmten Initialisierer delegieren muss, muss ein Initialisierungsprogramm für den Komfort einen entsprechenden Initialisierer aufrufen irgendwann. Dies ist erforderlich, um die zu initialisierende Instanz vollständig zu initialisieren.

Wenn beide Modellklassen implementiert sind, ist es an der Zeit, die ViewController und AddItemViewController Klassen. Beginnen wir mit dem letzteren.

2. Umgestaltung AddItemViewController

Schritt 1: Aktualisieren Sie die AddItemViewControllerDelegate Protokoll

Die einzigen Änderungen, die wir vornehmen müssen AddItemViewController Klasse sind mit der verwandt AddItemViewControllerDelegate Protokoll. Ändern Sie in der Protokolldeklaration den Typ von didAddItem von String zu Machen, die Modellklasse, die wir zuvor implementiert haben.

Protokoll AddItemViewControllerDelegate Funktionscontroller (_ Controller: AddItemViewController, didAddItem: ToDo)

Schritt 2: Aktualisieren Sie die erstellen(_:) Aktion

Das bedeutet, dass wir auch das aktualisieren müssen erstellen(_:) Aktion, in der wir die Delegat-Methode aufrufen. In der aktualisierten Implementierung erstellen wir eine Machen Beispiel, übergeben Sie es an die Delegat-Methode.

@IBAction func create (_ sender: Any) wenn let name = textField.text // Element erstellen let item = ToDo (name: name, done: false) // Delegiert delegieren? .Controller (self, didAddItem: item )

3. Umgestaltung ViewController

Schritt 1: Aktualisieren Sie die Artikel Eigentum

Das ViewController Der Unterricht erfordert etwas mehr Arbeit. Wir müssen zuerst den Typ des ändern Artikel Eigentum an [Machen], ein Array von Machen Instanzen.

var items: [ToDo] = [] didSet (oldValue) let hasItems = items.count> 0 tableView.isHidden =! hasItems messageLabel.isHidden = hasItems

Schritt 2: Table View-Datenquellenmethoden

Dies bedeutet auch, dass wir einige andere Methoden, wie die tableView (_: cellForRowAt :) Methode unten gezeigt. Weil der Artikel Array enthält jetzt Machen In manchen Fällen ist das Prüfen, ob ein Element als erledigt markiert ist, viel einfacher. Wir verwenden den ternären bedingten Operator von Swift, um den Zubehörtyp der Tabellensichtzelle zu aktualisieren.

func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell // Element abrufen lassen item = items [indexPath.row] // Dequeue Cell let cell = tableView.dequeueReusableCell (withIdentifier: "TableViewCell", für: indexPath) ) // Zelle konfigurieren cell.textLabel? .Text = item.name cell.accessoryType = item.done? .checkmark: .none Rückgabezelle

Wenn der Benutzer ein Element löscht, müssen wir nur das aktualisieren Artikel Eigenschaft durch Entfernen der entsprechenden Machen Beispiel. Dies spiegelt sich in der Umsetzung des tableView (_: commit: forRowAt :) Methode unten gezeigt.

func tableView (_ tableView: UITableView, Bearbeitungsschnitt übernehmen: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) if processingStyle == .delete // Aktualisierungselemente items.remove (at: indexPath.row) // Tabellenansicht tableView.deleteRows (at : [indexPath], mit: .right) // Status speichern saveItems ()

Schritt 3: Delegierte Methoden für die Tabellensicht

Der Status eines Elements wird aktualisiert, wenn der Benutzer auf eine Zeile tippt tableView (_: didSelectRowAt :) Methode. Die Umsetzung davon UITableViewDelegate Methode ist viel einfacher dank der Machen Klasse.

func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) tableView.deselectRow (at: indexPath, animiert: true) // Element abrufen item = items [indexPath.row] // Element auf Element item.done =! done // Zelle abrufen lassen cell = tableView.cellForRow (at: indexPath) // Zelle aktualisieren? .accessoryType = item.done? .checkmark: .none // Status speichern saveItems ()

Die entsprechende Machen Die Instanz wird aktualisiert, und diese Änderung wird in der Tabellensicht angezeigt. Um den Zustand zu speichern, rufen wir auf saveItems () anstatt saveCheckedItems ().

Schritt 4: Fügen Sie Elementansicht-Controller-Delegatmethoden hinzu

Weil wir das aktualisiert haben AddItemViewControllerDelegate Protokoll, müssen wir auch das aktualisieren ViewControllerImplementierung dieses Protokolls. Die Änderung ist jedoch einfach. Wir müssen nur die Methodensignatur aktualisieren.

func controller (_ controller: AddItemViewController, didAddItem: ToDo) // Aktualisieren der Datenquelle items.append (didAddItem) // Status speichern saveItems () // Tabellensicht neu laden tableView.reloadData () // Ablehnen Element hinzufügen Controller abbrechen ( animiert: true)

Schritt 5: Artikel speichern

Das pathForItems () Methode

Anstatt die Elemente in der Datenbank der Benutzervorgaben zu speichern, speichern wir sie im Dokumentenverzeichnis der Anwendung. Bevor wir das aktualisieren loadItems () und saveItems () Methoden, implementieren wir eine Hilfsmethode namens pathForItems (). Die Methode ist privat und gibt einen Pfad zurück, den Ort der Elemente im Dokumentenverzeichnis.

private func pathForItems () -> String guard let documentsDirectory = NSSearchPathForDirectoriesInDomains (.documentDirectory, .userDomainMask, true) .first, let url = URL (string: documentsDirectory) else fatalError ("Verzeichnis nicht gefunden") return url. appendingPathComponent ("items"). pfad

Wir rufen zunächst den Pfad zum Dokumentenverzeichnis in der Sandbox der Anwendung ab, indem Sie ihn aufrufen NSSearchPathForDirectoriesInDomains (_: _: _ :). Da diese Methode ein Array von Strings zurückgibt, greifen wir das erste Element auf.

Beachten Sie, dass wir eine verwenden bewachen Anweisung, um sicherzustellen, dass der Wert von zurückgegeben wird NSSearchPathForDirectoriesInDomains (_: _: _ :) ist gültig. Wenn dieser Vorgang fehlschlägt, wird ein schwerwiegender Fehler ausgegeben. Dies beendet die Anwendung sofort. Warum machen wir das? Wenn das Betriebssystem den Pfad zum Dokumentenverzeichnis nicht übergeben kann, haben wir größere Probleme.

Der Wert, aus dem wir zurückkehren pathForItems () setzt sich aus dem Pfad zum Dokumentenverzeichnis mit der Zeichenfolge zusammen "Artikel" daran angehängt.

Das loadItems () Methode

Die loadItems-Methode ändert sich ziemlich. Wir speichern zuerst das Ergebnis von pathForItems () in einer Konstante, Pfad. Das Archiv, das in diesem Pfad archiviert wurde, wird dann aus dem Archiv entpackt und auf ein optionales Array von heruntergespielt Machen Instanzen. Wir verwenden die optionale Bindung, um das optionale Paket zu entpacken und einer Konstante zuzuordnen, Artikel. In dem ob Klausel weisen wir den Wert zu, der in gespeichert ist Artikel zum Artikel Eigentum.

private func loadItems () let path = pathForItems () wenn let Elemente = NSKeyedUnarchiver.unarchiveObject (withFile: path) als? [ToDo] self.items = Elemente

Das saveItems () Methode

Das saveItems () Methode ist kurz und einfach. Wir speichern das Ergebnis von pathForItems () in einer Konstante, Pfad, und aufrufen archiveRootObject (_: toFile :) auf NSKeyedArchiver, vorbei in der Artikel Eigentum und Pfad. Das Ergebnis der Operation wird auf die Konsole gedruckt.

private func saveItems () let path = pathForItems () wenn NSKeyedArchiver.archiveRootObject (self.items, toFile: path) print ("Erfolgreich gespeichert") else print ("Speichern fehlgeschlagen")

Schritt 6: Aufräumen

Beenden wir mit dem lustigen Teil und löschen Sie den Code. Beginnen Sie mit dem Entfernen der checkedItems Eigentum an der Spitze, da wir es nicht mehr brauchen. Als Ergebnis können wir auch die entfernen loadCheckedItems () und saveCheckedItems () Methoden und jeder Hinweis auf diese Methoden in der ViewController Klasse.

Erstellen Sie die Anwendung, und führen Sie sie aus, um festzustellen, ob alles noch funktioniert. Das Datenmodell macht den Code der Anwendung wesentlich einfacher und zuverlässiger. Danke an die Machen Klasse ist das Verwalten der Elemente in unserer Liste jetzt viel einfacher und weniger fehleranfällig.

Fazit

In dieser Lektion haben wir das Datenmodell unserer Anwendung überarbeitet. Sie haben mehr über objektorientierte Programmierung und Vererbung gelernt. Die Instanzinitialisierung ist ein wichtiges Konzept in Swift. Vergewissern Sie sich daher, dass Sie wissen, was wir in dieser Lektion behandelt haben. Weitere Informationen zur Initialisierung und Initialisierung von Delegierungen finden Sie in der Swift-Programmiersprache.

In der Zwischenzeit können Sie einige unserer anderen Kurse und Tutorials zur Entwicklung von iOS in Swift Language ausprobieren!