Swift From Scratch Initialisering och Initializer Delegation

I den tidigare lektionen av Swift From Scratch skapade vi en funktionell applikation. Datamodellen kan dock använda viss kärlek. I den här sista lektionen kommer vi att reflektera datamodellen genom att implementera en anpassad modellklass.

1. Datamodellen

Den datamodell som vi ska genomföra innehåller två klasser, a Uppgift klass och a Att göra klass som ärar från Uppgift klass. Medan vi skapar och genomför dessa modellklasser fortsätter vi vår utforskning av objektorienterad programmering i Swift. I den här lektionen zoomar vi in ​​på initialiseringen av klassinstanser och vilken roll arv spelar under initialiseringen.

De Uppgift Klass

Låt oss börja med genomförandet av Uppgift klass. Skapa en ny Swift-fil genom att välja Ny> Fil ... från Xcode s Fil meny. Välja Swift File från iOS> Källa sektion. Namnge filen Task.swift och slå Skapa.

Den grundläggande implementeringen är kort och enkel. De Uppgift klass ärver från NSObject, definierad i fundament ramverk, och har en variabel egenskap namn av typ Sträng. Klassen definierar två initialisatorer, i det() och init (namn :). Det finns några detaljer som kan leda till dig, så låt mig förklara vad som händer.

importera Foundation class Uppgift: NSObject var namn: String bekvämlighet överstyra init () self.init (namn: "Ny uppgift") init (namn: String) self.name = name

Eftersom det i det() Metoden definieras också i NSObject klass måste vi prefixera initialiseraren med åsidosätta nyckelord. Vi omfattade övergripande metoder tidigare i denna serie. I i det() metod, vi åberopar init (namn :) metod, passerar in "Ny uppgift" som värdet för namn parameter.

De init (namn :) Metoden är en annan initialiserare, som accepterar en enda parameter namn av typ Sträng. I denna initialiserare, värdet av namn parametern är tilldelad till namn fast egendom. Detta är lätt nog att förstå. Höger?

Utnämnda och bekvämlighetsinitialiserare

Vad är det med bekvämlighet sökord som prefixar i det() metod? Klasser kan ha två typer av initialisatorer, betecknad initialiserare och bekvämlighet initializers. Bekvämlighet initialisatorer är prefixed med bekvämlighet sökord, vilket innebär att init (namn :) är en utsettsinitierare. Varför är det så? Vad är skillnaden mellan utvalda och bekvämlighetsinitialiserare?

Betecknade initialisatorer helt initialisera en instans av en klass, vilket innebär att varje egenskap hos förekomsten har ett initialvärde efter initialiseringen. Titta på Uppgift klass, till exempel ser vi att namn egenskapen är inställd med värdet av namn parameter för init (namn :) initierare. Resultatet efter initialiseringen är en helt initialiserad Uppgift exempel.

Bekvämlighet initialisatorer, dock lita på en utsettsinitierare för att skapa en helt initialiserad instans av klassen. Det är därför i det() initieraren av Uppgift klassen påstår init (namn :) initierare i dess genomförande. Detta kallas initieringsdelegation. De i det() initieraren delegerar initiering till en utsettsinitierare för att skapa en fullständigt initierad instans av Uppgift klass.

Bekvämlighet initialisatorer är valfria. Inte varje klass har en bekvämhetsinitierare. Utvalda initierare krävs, och en klass måste ha minst en utsettsinitierare för att skapa en helt initialiserad instans av sig själv.

De NSCoding Protokoll

Genomförandet av Uppgift klassen är dock inte komplett. Senare i den här lektionen skriver vi en rad av Att göra instanser till disk. Detta är endast möjligt om instanser av Att göra klassen kan kodas och avkodas.

Oroa dig inte, men det här är inte raketvetenskap. Vi behöver bara göra Uppgift och Att göra klasser överensstämmer med NSCoding protokoll. Det är därför Uppgift klass ärver från NSObject klass sedan NSCoding protokollet kan endast genomföras av klasser som ärar direkt eller indirekt från NSObject. Som NSObject klass, the NSCoding protokollet definieras i fundament ramverk.

Att anta ett protokoll är något som vi redan omfattas i denna serie, men det finns några gotchas som jag vill påpeka. Låt oss börja med att berätta för kompilatorn att Uppgift klassen överensstämmer med NSCoding protokoll.

importera stiftelsens klass Uppgift: NSObject, NSCoding var namn: String ...

Därefter måste vi genomföra de två metoder som deklarerats i NSCoding protokoll, init? (kodare :) och koda (med :). Genomförandet är enkelt om du är bekant med NSCoding protokoll.

importera Foundation Class Uppgift: NSObject, NSCoding var namn: String @objc krävs init? (kodare aDecoder: NSCoder) name = aDecoder.decodeObject (förKey: "name") som! String @objc func kodar (med aCoder: NSCoder) aCoder.encode (namn, förKey: "namn") överskridande init () self.init (namn: "Ny uppgift") init (namn: String) self.name = name

De init? (kodare :) initialiseraren är en utsedd initialiserare som initierar a Uppgift exempel. Även om vi implementerar init? (kodare :) metod för att överensstämma med NSCoding protokoll, behöver du aldrig direkt använda denna metod. Detsamma gäller för koda (med :), som kodar för en förekomst av Uppgift klass.

De nödvändig sökord som prefixar init? (kodare :) Metod indikerar att varje underklass av Uppgift klassen behöver genomföra denna metod. De nödvändig sökordet gäller bara initialisatorer, varför vi inte behöver lägga till det i koda (med :) metod.

Innan vi går vidare måste vi prata om @objc attribut. Eftersom det NSCoding protokollet är ett mål-C-protokoll, protokoll överensstämmelse kan bara kontrolleras genom att lägga till @objc attribut. I Swift finns det inte något sådant som protokollkonformitet eller valfria protokollmetoder. Med andra ord, om en klass följer en viss protokoll, verifierar compilatören och förväntar sig att varje metod i protokollet är implementerat.

De Att göra Klass

Med Uppgift klass implementeras, det är dags att genomföra Att göra klass. Skapa en ny Swift-fil och namnge den ToDo.swift. Låt oss titta på genomförandet av Att göra klass.

importera grundklass ToDo: Uppgift var gjort: Bool @objc krävs init? (kodare aDecoder: NSCoder) self.done = aDecoder.decodeBool (förKey: "done") super.init (kodare: aDecoder) @objc överstyra func kodar (med aCoder: NSCoder) aCoder.encode (done, forKey: "done") super.encode (med: aCoder) init (namn: String, gjort: Bool) self.done = done super.init : namn)  

De Att göra klass ärver från Uppgift klass och deklarerar en variabel egenskap Gjort av typ Bool. Förutom de två nödvändiga metoderna hos NSCoding protokoll som det ärar från Uppgift klass, deklarerar också en utsettsinitierare, init (name: gjort :).

Liksom i mål-C, den super sökordet refererar till superklassen, den Uppgift klass i detta exempel. Det finns en viktig detalj som förtjänar uppmärksamhet. Innan du åberopar init (namn :) Metod på superklassen, varje egendom som deklareras av Att göra klassen måste initialiseras. Med andra ord, före Att göra klass delegater initiering till sin superklass, varje egenskap som definieras av Att göra klassen måste ha ett giltigt initialvärde. Du kan verifiera detta genom att byta ordningsföljd och inspektera det fel som dyker upp.

Detsamma gäller för init? (kodare :) metod. Vi initierar först Gjort egendom innan man åberopar init? (kodare :) på superklassen.

Initialiserare och arv

När man arbetar med arv och initialisering finns det några regler att tänka på. Regeln för utvalda initierare är enkel.

  • En utpekad initialiserare behöver anropa en utsettsinitierare från sin superklass. I Att göra klass, till exempel, init? (kodare :) metod anropar init? (kodare :) Metoden för dess superklass. Detta kallas också delegera upp.

Reglerna för bekvämlighetsinitierare är lite mer komplexa. Det finns två regler att tänka på.

  • En bekvämhetsinitierare behöver alltid anropa en annan initialiserare av den klass som den definieras i. I Uppgift klass, till exempel, i det() Metoden är en bekvämhetsinitierare och delegatets initialisering till en annan initialiserare, init (namn :) i exemplet. Detta är känt som delegera över.
  • Även om en bekvämlighetsinitierare inte behöver delegera initieringen till en utsettsinitierare, behöver en bekvämlighetsinitierare ringa en utsedd initialiserare vid något tillfälle. Detta är nödvändigt för att initialisera den instans som initieras.

Med båda modellerna genomförda, är det dags att refactor ViewController och AddItemViewController klasser. Låt oss börja med den senare.

2. Refacto AddItemViewController

Steg 1: Uppdatera AddItemViewControllerDelegate Protokoll

De enda förändringar vi behöver göra i AddItemViewController klassen är relaterad till AddItemViewControllerDelegate protokoll. I protokolldeklarationen ändrar du typen av didAddItem från Sträng till Att göra, den modellklass vi genomförde tidigare.

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

Steg 2: Uppdatera skapa(_:) Verkan

Det betyder att vi också behöver uppdatera skapa(_:) Åtgärd där vi åberopar delegatmetoden. I det uppdaterade genomförandet skapar vi en Att göra exempel, överlämna det till delegatmetoden.

@IBAction func create (_ avsändare: Any) om låt namn = textField.text // Skapa objekt låtobjekt = ToDo (namn: namn, gjort: falskt) // Meddela Delegate delegate? .Controller (self, didAddItem: item )

3. Refacto ViewController

Steg 1: Uppdatera objekt Fast egendom

De ViewController klassen kräver lite mer arbete. Vi behöver först ändra typen av objekt egendom till [Att göra], en uppsättning av Att göra instanser.

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

Steg 2: Tabellvisning Datakälla Metoder

Detta innebär också att vi behöver refactor några andra metoder, som t.ex. Tableview (_: cellForRowAt :) metod som visas nedan. Eftersom det objekt array innehåller nu Att göra instanser, kontrollera om ett objekt är markerat som gjort är mycket enklare. Vi använder Swifts ternära villkorliga operatör för att uppdatera tabellvyns celltillbehörstyp.

func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell // Hämta Item let item = objekt [indexPath.row] // Dequeue Cell låt cell = tableView.dequeueReusableCell (withIdentifier: "TableViewCell", för: indexPath ) // Konfigurera Cell cell.textLabel? .Text = item.name cell.accessoryType = item.done? .checkmark: .none returnera cell

När användaren raderar ett objekt behöver vi bara uppdatera objekt egendom genom att ta bort motsvarande Att göra exempel. Detta återspeglas i genomförandet av Tableview (_: commit: forRowAt :) metod som visas nedan.

func tableView (_ tableView: UITableView, commit redigeringStyle: UITableViewCellEditingStyle, förRowAt indexPath: IndexPath) om redigeringStyle == .delete // Uppdatera objekt items.remove (på: indexPath.row) // Uppdatera tabellvy tableView.deleteRows : [indexPath], with: .right) // Spara State saveItems ()

Steg 3: Tabellvisning Delegate Methods

Uppdatera tillståndet för ett objekt när användaren kränger en rad hanteras i Tableview (_: didSelectRowAt :) metod. Genomförandet av detta UITableViewDelegate Metoden är mycket enklare tack vare Att göra klass.

func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) tableView.deselectRow (vid: indexPath, animated: true) // Hämta Item let item = objekt [indexPath.row] // Uppdatera objekt item.done =! gjort // Hämta cellen låt cellen = tableView.cellForRow (vid: indexPath) // Uppdatera cellcell? .accessoryType = item.done? .checkmark: .none // Spara State SaveItems ()

Korresponderande Att göra Instansen uppdateras, och denna ändring återspeglas av tabellvyn. För att rädda staten åberopar vi saveitems () istället för saveCheckedItems ().

Steg 4: Lägg till produkt Visa Controller Delegate Methods

Eftersom vi uppdaterade AddItemViewControllerDelegate protokoll måste vi också uppdatera ViewControllers genomförande av detta protokoll. Ändringen är dock enkel. Vi behöver bara uppdatera metodens signatur.

func controller (_ controller: AddItemViewController, didAddItem: ToDo) // Uppdatera datakälla items.append (didAddItem) // Spara State SaveItems () // Uppdatera tabellvyn tabellView.reloadData () // Avvisa Lägg till objekt Visa Controller avvisa animerad: true)

Steg 5: Spara artiklar

De pathForItems () Metod

I stället för att lagra objekten i databasen för användarens standard, kommer vi att lagra dem i programmets katalogkatalog. Innan vi uppdaterar loadItems () och saveitems () metoder, vi kommer att genomföra en hjälparmetod som heter pathForItems (). Metoden är privat och returnerar en sökväg, placeringen av objekten i dokumentkatalogen.

privat func pathForItems () -> String guard let documentsDirectory = NSSearchPathForDirectoriesInDomains (.documentDirectory, .userDomainMask, true). först, släpp url = URL (sträng: documentsDirectory) annars fatalError ("Dokumentkatalog ej hittad") returnera url. appendingPathComponent ("items"). sökväg

Vi hämtar först sökvägen till dokumentkatalogen i programmets sandlåda genom att åberopa NSSearchPathForDirectoriesInDomains (_: _: _ :). Eftersom den här metoden returnerar en rad strängar tar vi det första objektet.

Observera att vi använder en vakt uttalande för att säkerställa att värdet returneras av NSSearchPathForDirectoriesInDomains (_: _: _ :) är giltig. Vi slår ett dödligt fel om denna operation misslyckas. Detta avslutar omedelbart ansökan. Varför gör vi det här? Om operativsystemet inte kan ge oss vägen till dokumentkatalogen har vi större problem att oroa sig för.

Värdet vi återvänder från pathForItems () består av sökvägen till dokumentkatalogen med strängen "objekt" bifogas det.

De loadItems () Metod

Metoden loadItems ändras ganska lite. Vi lagrar först resultatet av pathForItems () i en konstant, väg. Vi arkiverar sedan objektet som är arkiverat på den sökvägen och sänker det till en valfri uppsättning av Att göra instanser. Vi använder valfri bindning för att ta bort valfri och tilldela den till en konstant, objekt. I om klausul tilldelar vi värdet lagrat i objekt till objekt fast egendom.

privat func loadItems () let path = pathForItems () om låter objekt = NSKeyedUnarchiver.unarchiveObject (withFile: path) som? [ToDo] self.items = items

De saveitems () Metod

De saveitems () Metoden är kort och enkel. Vi lagrar resultatet av pathForItems () i en konstant, väg, och åberopa archiveRootObject (_: toFile :)NSKeyedArchiver, passerar i objekt egendom och väg. Vi skriver ut resultatet av operationen till konsolen.

privat func saveItems () let path = pathForItems () om NSKeyedArchiver.archiveRootObject (self.items, toFile: path) skriv ut ("Sparade framgångsrikt") else print ("Spara misslyckades")

Steg 6: Rengöring

Låt oss avsluta med den roliga delen, radera kod. Börja med att ta bort checkedItems egendom på toppen eftersom vi inte längre behöver det. Som ett resultat kan vi också ta bort loadCheckedItems () och saveCheckedItems () metoder och alla hänvisningar till dessa metoder i ViewController klass.

Bygg och kör programmet för att se om allt fortfarande fungerar. Datamodellen gör programmets kod mycket enklare och mer tillförlitlig. Tack vare Att göra klass, hantera objekten i vår lista mycket är nu lättare och mindre felaktigt.

Slutsats

I denna lektion refactored vi datormodellen i vår ansökan. Du lärde dig mer om objektorienterad programmering och arv. Instansinitiering är ett viktigt begrepp i Swift, så se till att du förstår vad vi har täckt i den här lektionen. Du kan läsa mer om initiering och initieringsdelegation i The Swift Programming Language.

Under tiden, kolla in några av våra andra kurser och handledning om Swift-språk iOS-utveckling!