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.
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.
Aufgabe
KlasseBeginnen 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?
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.
NSCoding
ProtokollDie 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.
Machen
KlasseMit 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.
Beim Umgang mit Vererbung und Initialisierung sind einige Regeln zu beachten. Die Regel für bestimmte Initialisierer ist einfach.
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.
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.Wenn beide Modellklassen implementiert sind, ist es an der Zeit, die ViewController
und AddItemViewController
Klassen. Beginnen wir mit dem letzteren.
AddItemViewController
AddItemViewControllerDelegate
ProtokollDie 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)
erstellen(_:)
AktionDas 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 )
ViewController
Artikel
EigentumDas 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
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 ()
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 ()
.
Weil wir das aktualisiert haben AddItemViewControllerDelegate
Protokoll, müssen wir auch das aktualisieren ViewController
Implementierung 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)
pathForItems ()
MethodeAnstatt 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.
loadItems ()
MethodeDie 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
saveItems ()
MethodeDas 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")
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.
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!