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.
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.
Uppgift
KlassLå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?
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.
NSCoding
ProtokollGenomfö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.
Att göra
KlassMed 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.
När man arbetar med arv och initialisering finns det några regler att tänka på. Regeln för utvalda initierare är enkel.
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å.
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.Med båda modellerna genomförda, är det dags att refactor ViewController
och AddItemViewController
klasser. Låt oss börja med den senare.
AddItemViewController
AddItemViewControllerDelegate
ProtokollDe 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)
skapa(_:)
VerkanDet 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 )
ViewController
objekt
Fast egendomDe 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
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 ()
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 ()
.
Eftersom vi uppdaterade AddItemViewControllerDelegate
protokoll måste vi också uppdatera ViewController
s 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)
pathForItems ()
MetodI 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.
loadItems ()
MetodMetoden 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
saveitems ()
MetodDe saveitems ()
Metoden är kort och enkel. Vi lagrar resultatet av pathForItems ()
i en konstant, väg
, och åberopa archiveRootObject (_: toFile :)
på 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")
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.
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!