Swift from Scratch initialisatie en initialisatie-delegatie

In de vorige les van Swift From Scratch hebben we een functionele taak gemaakt. Het datamodel zou echter wat liefde kunnen gebruiken. In deze laatste les gaan we het gegevensmodel refactoren door een aangepaste modelklasse te implementeren.

1. Het datamodel

Het gegevensmodel dat we gaan implementeren, omvat twee klassen, een Taak klasse en een Te doen klasse die erft van de Taak klasse. Terwijl we deze modelklassen maken en implementeren, zullen we onze verkenning van objectgeoriënteerd programmeren in Swift voortzetten. In deze les zullen we inzoomen op de initialisatie van klasseninstanties en welke rolovername wordt afgespeeld tijdens de initialisatie.

De Taak Klasse

Laten we beginnen met de implementatie van de Taak klasse. Maak een nieuw Swift-bestand door te selecteren Nieuw> Bestand ... van Xcode's het dossier menu. Kiezen Swift-bestand van de iOS> Bron sectie. Geef het bestand een naam Task.swift en druk op creëren.

De basisimplementatie is kort en eenvoudig. De Taak klasse erft van NSObject, gedefinieerd in de fundament framework en heeft een variabel eigendom naam van type Draad. De klasse definieert twee initializers, in het() en init (naam :). Er zijn een paar details die je in de war kunnen brengen, dus laat me uitleggen wat er aan de hand is.

import Foundation class Taak: NSObject var name: String gemakoverschrijving init () self.init (naam: "Nieuwe taak") init (naam: String) self.name = naam

Omdat het in het() methode is ook gedefinieerd in de NSObject klasse, moeten we de initializer een voorvoegsel geven met de override trefwoord. We hebben eerder in deze serie overweldigende methoden behandeld. In de in het() methode, roepen we de init (naam :) methode, binnenkomen "Nieuwe taak" als de waarde voor de naam parameter.

De init (naam :) methode is een andere initializer en accepteert een enkele parameter naam van type Draad. In deze initializer is de waarde van de naam parameter is toegewezen aan de naam eigendom. Dit is gemakkelijk genoeg om te begrijpen. Rechts?

Aangewezen en gemak initializers

Wat is er met de gemak trefwoord voorvoegsel de in het() methode? Klassen kunnen twee soorten initializers hebben, aangewezen initializers en gemak initialiseerders. Convenience-initializers worden voorafgegaan door de gemak zoekwoord, wat impliceert dat init (naam :) is een aangewezen initialisator. Waarom is dat? Wat is het verschil tussen aangewezen en gemak-initializers?

Aangewezen initializers initialiseer een instantie van een klasse volledig, wat betekent dat elke eigenschap van de instantie een initiële waarde heeft na initialisatie. Kijken naar de Taak klasse, bijvoorbeeld, we zien dat het naam eigenschap wordt ingesteld met de waarde van de naam parameter van de init (naam :) initializer. Het resultaat na initialisatie is volledig geïnitialiseerd Taak aanleg.

Gemak-initializers, vertrouw echter op een aangewezen initialisatieprogramma om een ​​volledig geïnitialiseerd exemplaar van de klasse te maken. Dat is waarom het in het() initialisator van de Taak klasse roept de init (naam :) initialisator bij de implementatie. Dit wordt aangeduid als initialisator delegatie. De in het() initializer delegeert de initialisatie naar een aangewezen initializer om een ​​volledig geïnitialiseerd exemplaar van de Taak klasse.

Gemak-initializers zijn optioneel. Niet elke klas heeft een gemakinitialisatie. Aangewezen initializers zijn vereist en een klasse moet ten minste één aangewezen initialisator hebben om een ​​volledig geïnitialiseerd exemplaar van zichzelf te maken.

De NSCoding Protocol

De implementatie van de Taak klas is echter niet compleet. Later in deze les zullen we een reeks van schrijven Te doen instanties naar schijf. Dit is alleen mogelijk als instanties van de Te doen klasse kan worden gecodeerd en gedecodeerd.

Maak je geen zorgen, dit is geen rocket science. We hoeven alleen het te maken Taak en Te doen klassen voldoen aan de NSCoding protocol. Dat is waarom het Taak klasse erft van de NSObject klasse sinds de NSCoding protocol kan alleen worden geïmplementeerd door klassen die rechtstreeks of onrechtstreeks overerven van NSObject. Zoals de NSObject klas, de NSCoding protocol is gedefinieerd in de fundament kader.

Het aannemen van een protocol is iets dat we al in deze serie behandelen, maar er zijn een paar valstrikken waar ik op wil wijzen. Laten we beginnen met de compiler te vertellen dat het Taak klas voldoet aan de NSCoding protocol.

import Foundation class Task: NSObject, NSCoding var name: String ...

Vervolgens moeten we de twee methoden implementeren die zijn gedeclareerd in de NSCoding protocol, init? (coder :) en coderen (met :). De implementatie is eenvoudig als u bekend bent met de NSCoding protocol.

import Foundation class Taak: NSObject, NSCoding var naam: String @objc vereist init? (coder aDecoder: NSCoder) name = aDecoder.decodeObject (forKey: "name") as! String @objc func-codering (met aCoder: NSCoder) aCoder.encode (naam, forKey: "naam") gemak opheffen init () self.init (naam: "Nieuwe taak") init (naam: tekenreeks) self.name = naam

De init? (coder :) initializer is een aangewezen initialisator die a initialiseert Taak aanleg. Ook al implementeren we de init? (coder :) methode om te voldoen aan de NSCoding protocol, hoeft u deze methode nooit rechtstreeks in te roepen. Hetzelfde geldt voor coderen (met :), die een exemplaar van de codering codeert Taak klasse.

De verplicht trefwoord voorvoegsel de init? (coder :) methode geeft aan dat elke subklasse van de Taak klasse moet deze methode implementeren. De verplicht sleutelwoord is alleen van toepassing op initializers, daarom hoeven we het niet toe te voegen aan de coderen (met :) methode.

Voordat we verder gaan, moeten we praten over de @objc attribuut. Omdat het NSCoding protocol is een Objective-C-protocol, protocol conformiteit kan alleen worden gecontroleerd door de @objc attribuut. In Swift bestaat er niet zoiets als protocol-conformiteit of optionele protocolmethoden. Met andere woorden, als een klasse een bepaald protocol volgt, verifieert de compiler en verwacht dat elke methode van het protocol geïmplementeerd is.

De Te doen Klasse

Met de Taak klasse geïmplementeerd, is het tijd om het te implementeren Te doen klasse. Maak een nieuw Swift-bestand en noem het ToDo.swift. Laten we kijken naar de implementatie van de Te doen klasse.

import Foundation-klasse ToDo: taak var done: Bool @objc vereist init? (coder aDecoder: NSCoder) self.done = aDecoder.decodeBool (forKey: "done") super.init (coder: aDecoder) @objc overschrijven func coderen (met aCoder: NSCoder) aCoder.encode (done, forKey: "done") super.encode (met: aCoder) init (naam: String, done: Bool) self.done = done super.init (naam : naam)  

De Te doen klasse erft van de Taak class en verklaart een variabel eigendom gedaan van type Bool. Naast de twee vereiste methoden van de NSCoding protocol dat het erft van de Taak klasse, wordt ook een aangewezen initializer aangegeven, init (naam: gedaan :).

Net als in Objective-C, de super sleutelwoord verwijst naar de superklasse, de Taak klasse in dit voorbeeld. Er is een belangrijk detail dat aandacht verdient. Voordat u de init (naam :) methode op de superklasse, elke eigenschap aangegeven door de Te doen klasse moet worden geïnitialiseerd. Met andere woorden, vóór de Te doen class delegates initialisatie naar zijn superklasse, elke eigenschap gedefinieerd door de Te doen klasse moet een geldige beginwaarde hebben. U kunt dit controleren door de volgorde van de overzichten om te schakelen en de fout te bekijken die opduikt.

Hetzelfde geldt voor de init? (coder :) methode. We initialiseren eerst de gedaan eigenschap vóór het aanroepen init? (coder :) op de superklasse.

Initializers en overerving

Als het gaat om vererving en initialisatie, zijn er enkele regels die u in gedachten moet houden. De regel voor aangewezen initializers is eenvoudig.

  • Een aangewezen initialisator moet een aangewezen initialisator uit zijn superklasse oproepen. In de Te doen klasse, bijvoorbeeld de init? (coder :) methode roept de init? (coder :) methode van zijn superklasse. Dit wordt ook wel aangeduid als afvaardigen.

De regels voor het gemak initializers zijn een beetje ingewikkelder. Er zijn twee regels om in gedachten te houden.

  • Een gemakinitialisator moet altijd een andere initializer aanroepen van de klasse waarin deze is gedefinieerd Taak klasse, bijvoorbeeld de in het() methode is een gemakinitialisatie en delegeert de initialisatie naar een andere initializer, init (naam :) in het voorbeeld. Dit staat bekend als delegeren over.
  • Hoewel een gemakinitialisator de initialisatie niet hoeft te delegeren naar een aangewezen initialisator, moet een gemakinitialisator een aangewezen initialisator bellen. op een gegeven moment. Dit is nodig om het exemplaar dat wordt geïnitialiseerd, volledig te initialiseren.

Als beide modelklassen zijn geïmplementeerd, is het tijd om de ViewController en AddItemViewController klassen. Laten we beginnen met het laatste.

2. Refactoring AddItemViewController

Stap 1: werk het AddItemViewControllerDelegate Protocol

De enige veranderingen die we moeten aanbrengen in de AddItemViewController klasse zijn gerelateerd aan de AddItemViewControllerDelegate protocol. Wijzig in de protocolverklaring het type didAddItem van Draad naar Te doen, de modelklasse die we eerder hebben geïmplementeerd.

protocol AddItemViewControllerDelegate func controller (_ controller: AddItemViewController, didAddItem: ToDo)

Stap 2: werk het te maken (_ :) Actie

Dit betekent dat we ook het te maken (_ :) actie waarin we de gedelegeerde methode gebruiken. In de bijgewerkte implementatie maken we een Te doen bijvoorbeeld door deze door te geven aan de gedelegeerde methode.

@IBAction func create (_ afzender: Any) if let name = textField.text // Item maken item maken = ToDo (naam: naam, gereed: false) // Melden Delegate delegate? .Controller (self, didAddItem: item )

3. Refactoring ViewController

Stap 1: werk het items Eigendom

De ViewController klas vereist wat meer werk. We moeten eerst het type van de wijzigen items eigendom aan [Te doen], een reeks van Te doen instanties.

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

Stap 2: Tabel Methoden voor gegevensbron weergeven

Dit betekent ook dat we een aantal andere methoden moeten refactoren, zoals de tableView (_: cellForRowAt :) methode hieronder weergegeven. Omdat het items array bevat nu Te doen exemplaren, controleren of een item als voltooid is gemarkeerd, is veel eenvoudiger. We gebruiken de ternaire conditionele operator van Swift om het accessoire type van de tafelviewcel bij te werken.

func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell // Fetch Item let item = items [indexPath.row] // Cel van wachtrij laten cel = tableView.dequeueReusableCell (withIdentifier: "TableViewCell", voor: indexPath ) // Configell Cell cell.textLabel? .Text = item.name cell.accessoryType = item.done? .checkmark: .niet retourcel

Wanneer de gebruiker een item verwijdert, hoeven we alleen het items eigendom door het overeenkomstige te verwijderen Te doen aanleg. Dit komt tot uiting in de implementatie van de tableView (_: commit: forRowAt :) methode hieronder weergegeven.

func tableView (_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) if editingStyle == .delete // Update Items items.remove (at: indexPath.row) // Update tabel TabelView.deleteRows (op : [indexPath], met: .Right) // Save State saveItems ()

Stap 3: Tabel View Delegate Methods

De status van een item bijwerken wanneer de gebruiker op een rij tikt, wordt afgehandeld in de tableView (_: didSelectRowAt :) methode. De implementatie hiervan UITableViewDelegate methode is veel eenvoudiger dankzij de Te doen klasse.

func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) tableView.deselectRow (at: indexPath, geanimeerd: true) // Item ophalen item item = items [indexPath.row] // Itemitem aanpassen.done =! item. klaar // Cel ophalen cel laten staan ​​= tableView.cellForRow (at: indexPath) // Cel bijwerken? .accessoryType = item.done? .checkmark: .none // Status opslaan saveItems ()

De bijbehorende Te doen exemplaar wordt bijgewerkt en deze wijziging wordt weerspiegeld in de tabelweergave. Om de staat te redden, roepen we aan saveItems () in plaats van saveCheckedItems ().

Stap 4: Add Item View Controller Delegate Methods

Omdat we de AddItemViewControllerDelegate protocol, we moeten ook de ViewControllerde implementatie van dit protocol. De verandering is echter eenvoudig. We hoeven alleen de handtekening van de methode bij te werken.

func controller (_ controller: AddItemViewController, didAddItem: ToDo) // Update Data Source items.append (didAddItem) // Save State saveItems () // Reload Table View tableView.reloadData () // Negeer Add Item View Controller negeren (verwijderen geanimeerd: waar)

Stap 5: Items opslaan

De pathForItems () Methode

In plaats van de items op te slaan in de database met gebruikersstandaarden, gaan we ze opslaan in de documentenmap van de toepassing. Voordat we de loadItems () en saveItems () methoden, we gaan een helper-methode implementeren genaamd pathForItems (). De methode is privé en retourneert een pad, de locatie van de items in de documentenmap.

private func pathForItems () -> String guard let documentsDirectory = NSSearchPathForDirectoriesInDomains (.documentDirectory, .userDomainMask, true) .eerst laat url = URL (string: documentsDirectory) else fatalError ("Documents Directory Not Found") return url. appendPathComponent ("items"). path

We halen eerst het pad naar de documentenmap in de sandbox van de toepassing op door op te roepen NSSearchPathForDirectoriesInDomains (_: _: _ :). Omdat deze methode een reeks tekenreeksen retourneert, pakken we het eerste item.

Merk op dat we een a gebruiken bewaker verklaring om ervoor te zorgen dat de waarde wordt geretourneerd door NSSearchPathForDirectoriesInDomains (_: _: _ :) is geldig. We geven een fatale fout als deze bewerking mislukt. Hiermee wordt de toepassing onmiddellijk beëindigd. Waarom doen we dit? Als het besturingssysteem ons het pad naar de documentenmap niet kan doorgeven, hebben we grotere problemen om ons zorgen over te maken.

De waarde waar we van terugkomen pathForItems () is samengesteld uit het pad naar de documentenmap met de tekenreeks "Items" eraan toegevoegd.

De loadItems () Methode

De methode loadItems verandert nogal. We slaan eerst het resultaat op van pathForItems () in een constante, pad. Vervolgens verwijderen we het object dat op dat pad is gearchiveerd en wordt dit naar een optionele array gedowngraded Te doen instances. We gebruiken optionele binding om de optionele uit te pakken en toe te wijzen aan een constante, items. In de als clausule, we wijzen de waarde toe die is opgeslagen in items naar de items eigendom.

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

De saveItems () Methode

De saveItems () methode is kort en eenvoudig. We slaan het resultaat op van pathForItems () in een constante, pad, en aanroepen archiveRootObject (_: toFile :) op NSKeyedArchiver, passeren in de items eigendom en pad. We drukken het resultaat van de bewerking op de console af.

private func saveItems () let path = pathForItems () if NSKeyedArchiver.archiveRootObject (self.items, toFile: pad) print ("Successfully Saved") else print ("Saving Failed")

Stap 6: Ruim op

Laten we eindigen met het leuke gedeelte, code verwijderen. Begin met het verwijderen van de checkedItems eigendom aan de top omdat we het niet langer nodig hebben. Als gevolg hiervan kunnen we ook het loadCheckedItems () en saveCheckedItems () methoden en elke verwijzing naar deze methoden in de ViewController klasse.

Bouw en voer de applicatie uit om te zien of alles nog werkt. Het datamodel maakt de code van de toepassing veel eenvoudiger en betrouwbaarder. Dankzij de Te doen klasse, het beheer van de items in onze lijst veel is nu eenvoudiger en minder foutgevoelig.

Conclusie

In deze les hebben we het datamodel van onze applicatie verfijnd. Je hebt meer geleerd over objectgeoriënteerd programmeren en overerven. Instance-initialisatie is een belangrijk concept in Swift, dus zorg ervoor dat je begrijpt wat we in deze les hebben behandeld. U kunt meer lezen over initialisatie en initialisatie van delegaties in The Swift Programming Language.

Bekijk in de tussentijd enkele van onze andere cursussen en zelfstudies over de ontwikkeling van de Swift-taal voor iOS!