I den forrige leksjonen Swift From Scratch opprettet vi et funksjonelt oppgaveprogram. Datamodellen kan imidlertid bruke litt kjærlighet. I denne siste leksjonen skal vi reflektere datamodellen ved å implementere en tilpasset modellklasse.
Datamodellen vi skal implementere inkluderer to klasser, a Oppgave
klasse og a Å gjøre
klasse som arver fra Oppgave
klasse. Mens vi lager og implementerer disse modellklassene, fortsetter vi utforskningen av objektorientert programmering i Swift. I denne leksjonen vil vi zoome inn på initialiseringen av klassetilfeller og hvilken rolle arv spiller under initialiseringen.
Oppgave
KlasseLa oss starte med implementeringen av Oppgave
klasse. Opprett en ny Swift-fil ved å velge Ny> Fil ... fra Xcode er Fil Meny. Velge Swift File fra iOS> Kilde seksjon. Gi filen navnet Task.swift og treffer Skape.
Den grunnleggende implementeringen er kort og enkel. De Oppgave
Klassen arver fra NSObject
, definert i Fundament rammeverk, og har en variabel eiendom Navn
av type string
. Klassen definerer to initiativer, i det()
og init (navn :)
. Det er noen detaljer som kan gi deg opp, så la meg forklare hva som skjer.
importere Foundation Class Oppgave: NSObject var navn: String bekvemmelighet overstyre init () self.init (navn: "Ny oppgave") init (navn: String) self.name = name
Fordi det i det()
Metoden er også definert i NSObject
klasse, må vi prefikse initialisereren med overstyring
søkeord. Vi dekket overordnede metoder tidligere i denne serien. I i det()
Metode, vi påberoper init (navn :)
metode, passerer inn "Ny oppgave"
som verdien for Navn
parameter.
De init (navn :)
metoden er en annen initialiserer, aksepterer en enkelt parameter Navn
av type string
. I denne initialiseringen, verdien av Navn
parameteren er tilordnet til Navn
eiendom. Dette er lett nok til å forstå. Ikke sant?
Hva er med bekvemmelighet
søkeord som prefikserer i det()
metode? Klasser kan ha to typer initiatorer, utpekt initiativer og bekvemmelighet initializers. Convenience initializers er prefixed med bekvemmelighet
søkeord, noe som innebærer det init (navn :)
er en utpekt initialiserer. Hvorfor det? Hva er forskjellen mellom utpekte og bekvemmelighetsinitiatorer?
Utpekte initiativer fullstendig initialisere en forekomst av en klasse, noe som betyr at hver egenskap av forekomsten har en startverdi etter initialisering. Ser på Oppgave
klasse, for eksempel ser vi at Navn
Eiendommen er satt med verdien av Navn
parameteren til init (navn :)
initializer. Resultatet etter initialisering er en fullt initialisert Oppgave
forekomst.
Praktiske initiativer, imidlertid stole på en utpekt initialiserer for å lage en fullt initialisert forekomst av klassen. Det er derfor i det()
initialiserer av Oppgave
klassen påkaller init (navn :)
initialiserer i implementeringen. Dette kalles initieringsdelegasjon. De i det()
initialiserer initierer initialisering til en utpekt initialiserer for å opprette en fullt initialisert forekomst av Oppgave
klasse.
Praktiske initiativer er valgfrie. Ikke hver klasse har en bekvemmelighetsinitiator. Utpekte initiativer er påkrevd, og en klasse må ha minst en utpekt initialiserer for å lage en fullt initialisert forekomst av seg selv.
NSCoding
protokollGjennomføringen av Oppgave
klassen er ikke fullført, skjønt. Senere i denne leksjonen skriver vi en rekke Å gjøre
forekommer til disk. Dette er bare mulig hvis forekomster av Å gjøre
klassen kan kodes og dekodes.
Ikke vær redd, skjønt, dette er ikke rakettvitenskap. Vi trenger bare å lage Oppgave
og Å gjøre
klasser samsvarer med NSCoding
protokoll. Det er derfor Oppgave
Klassen arver fra NSObject
klasse siden NSCoding
protokollen kan bare implementeres av klasser som arver direkte eller indirekte fra NSObject
. Som NSObject
klasse, den NSCoding
protokollen er definert i Fundament rammeverk.
Å vedta en protokoll er noe vi allerede har dekket i denne serien, men det er noen få gotker som jeg vil påpeke. La oss begynne med å fortelle kompilatoren at Oppgave
klassen stemmer overens med NSCoding
protokollen.
importere grunnlagsklasse Oppgave: NSObject, NSCoding var navn: String ...
Deretter må vi implementere de to metodene deklarert i NSCoding
protokollen, init? (koder :)
og kode (med :)
. Gjennomføringen er grei hvis du er kjent med NSCoding
protokollen.
Import Foundation Class Oppgave: NSObject, NSCoding var navn: String @objc required init? (koder aDecoder: NSCoder) name = aDecoder.decodeObject (forKey: "navn") som! String @objc func kode (med aCoder: NSCoder) aCoder.encode (navn, forKey: "navn") bekvemmelighet overstyre init () self.init (navn: "Ny oppgave") init (navn: String) self.name = name
De init? (koder :)
initialiserer er en utpekt initialiserer som initialiserer a Oppgave
forekomst. Selv om vi implementerer init? (koder :)
metode for å tilpasse seg NSCoding
protokoll, trenger du aldri å påkalle denne metoden direkte. Det samme gjelder for kode (med :)
, som koder for en forekomst av Oppgave
klasse.
De nødvendig
søkeord som prefikserer init? (koder :)
Metode indikerer at hver underklasse av Oppgave
klassen må implementere denne metoden. De nødvendig
Søkeordet gjelder bare for initiativer, og derfor trenger vi ikke å legge det til i kode (med :)
metode.
Før vi går videre, må vi snakke om @objc
Egenskap. Fordi det NSCoding
protokollen er en mål-C-protokoll, protokoll overensstemmelse kan bare kontrolleres ved å legge til @objc
Egenskap. I Swift er det ikke slikt som protokollkonformitet eller valgfrie protokollmetoder. Med andre ord, hvis en klasse overholder en bestemt protokoll, verifiserer kompilatoren og forventer at alle metoder i protokollen er implementert.
Å gjøre
KlasseMed Oppgave
klasse implementert, er det på tide å implementere Å gjøre
klasse. Opprett en ny Swift-fil og gi den navnet ToDo.swift. La oss se på implementeringen av Å gjøre
klasse.
importere Foundation class ToDo: Oppgave var ferdig: Bool @objc kreves init? (koder aDecoder: NSCoder) self.done = aDecoder.decodeBool (forKey: "ferdig") super.init (koder: aDecoder) @objc overstyr func kode (med aCoder: NSCoder) aCoder.encode (ferdig, forKey: "ferdig") super.encode (med: aCoder) init (navn: String, ferdig: Bool) self.done = ferdig super.init : Navn)
De Å gjøre
Klassen arver fra Oppgave
klasse og erklærer en variabel egenskap ferdig
av type bool
. I tillegg til de to nødvendige metodene til NSCoding
protokoll som den arver fra Oppgave
klassen, erklærer den også en utpekt initialiserer, init (navn: gjort :)
.
Som i mål-C, super
søkeord refererer til superklassen, den Oppgave
klasse i dette eksemplet. Det er en viktig detalj som fortjener oppmerksomhet. Før du påkaller init (navn :)
metode på superklassen, hver eiendom deklarert av Å gjøre
klassen må initialiseres. Med andre ord, før Å gjøre
klasse delegater initialisering til sin superklasse, hver eiendom definert av Å gjøre
klassen må ha en gyldig startverdi. Du kan bekrefte dette ved å bytte rekkefølgen på setningene og inspisere feilen som dukker opp.
Det samme gjelder for init? (koder :)
metode. Vi initialiserer først ferdig
eiendom før påkalling init? (koder :)
på superklassen.
Når det gjelder arv og initialisering, er det noen regler å huske på. Regelen for utpekte initiativer er enkel.
Å gjøre
klasse, for eksempel, den init? (koder :)
metode påberoper seg init? (koder :)
metode for superklassen. Dette kalles også delegere opp.Reglene for bekvemmelighetsinitiatorer er litt mer komplekse. Det er to regler å huske på.
Oppgave
klasse, for eksempel, den i det()
Metoden er en praktisk initialiserer og delegerer initialisering til en annen initiator, init (navn :)
i eksemplet. Dette er kjent som delegere over.Med begge modellklasser implementert, er det på tide å refactor ViewController
og AddItemViewController
klasser. La oss starte med sistnevnte.
AddItemViewController
AddItemViewControllerDelegate
protokollDe eneste endringene vi må gjøre i AddItemViewController
klassen er relatert til AddItemViewControllerDelegate
protokoll. I protokolldeklarasjonen endrer du typen av didAddItem
fra string
til Å gjøre
, modellklassen vi implementerte tidligere.
protokoll AddItemViewControllerDelegate func controller (_ kontroller: AddItemViewController, didAddItem: ToDo)
skape(_:)
HandlingDette betyr at vi også må oppdatere skape(_:)
handling der vi anvender delegatemetoden. I den oppdaterte implementeringen lager vi en Å gjøre
eksempel, passerer den til delegatemetoden.
@IBAction func create (_ sender: Any) hvis la navn = textField.text // Opprett element let element = ToDo (navn: navn, ferdig: false) // Varsle Delegat delegat? .Controller (selvtillit, gjordeAddItem: )
ViewController
elementer
EiendomDe ViewController
klassen krever litt mer arbeid. Vi må først endre typen av elementer
eiendom til [Å gjøre]
, en rekke av Å gjøre
forekomster.
var elementer: [ToDo] = [] didSet (oldValue) la hasItems = items.count> 0 tableView.isHidden =! hasItems messageLabel.isHidden = hasItems
Dette betyr også at vi må refactor noen andre metoder, for eksempel Tableview (_: cellForRowAt :)
metode vist nedenfor. Fordi det elementer
array inneholder nå Å gjøre
forekomster, sjekker om et element er merket som gjort er mye enklere. Vi bruker Swifts ternære betingede operatør for å oppdatere tabellvisningscellens tilbehørstype.
func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell // Hente element let element = elementer [indexPath.row] // Dequeue Cell lar celle = tableView.dequeueReusableCell (withIdentifier: "TableViewCell", for: indexPath ) // Konfigurer Cell cell.textLabel? .Text = item.name cell.accessoryType = item.done? .checkmark: .none returner celle
Når brukeren sletter et element, trenger vi bare å oppdatere elementer
eiendom ved å fjerne tilsvarende Å gjøre
forekomst. Dette gjenspeiles i implementeringen av Tableview (_: begå: forRowAt :)
metode vist nedenfor.
func tableView (_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) hvis editingStyle == .delete // Oppdater elementer items.remove (på: indexPath.row) // Oppdater tabelloversikt tableView.deleteRows : [indexPath], med: .right) // Lagre stat saveItems ()
Oppdaterer tilstanden til et element når brukeren tapper en rad håndteres i Tableview (_: didSelectRowAt :)
metode. Gjennomføringen av dette UITableViewDelegate
Metoden er mye enklere takket være Å gjøre
klasse.
func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) tableView.deselectRow (ved: indexPath, animated: true) // Hente Item let item = elementer [indexPath.row] // Oppdater element item.done =! gjort // Hent celle la celle = tableView.cellForRow (ved: indexPath) // Oppdater Cell celle? .accessoryType = item.done? .checkmark: .none // Save State saveItems ()
Den tilsvarende Å gjøre
forekomsten er oppdatert, og denne endringen reflekteres av tabellvisningen. For å redde staten, påberoper vi oss saveItems ()
i stedet for saveCheckedItems ()
.
Fordi vi oppdaterte AddItemViewControllerDelegate
protokoll, må vi også oppdatere ViewController
Implementering av denne protokollen. Endringen er imidlertid enkel. Vi trenger bare å oppdatere metoden signaturen.
func controller (_controller: AddItemViewController, didAddItem: ToDo) // Oppdater datakilde items.append (didAddItem) // Lagre stat saveItems () // Oppdater tabelloversikt tableView.reloadData () // Avvis Legg til produkt Vis Controller avvis animert: sant)
pathForItems ()
MetodeI stedet for å lagre elementene i brukerstandarddatabasen, skal vi lagre dem i programmets katalog. Før vi oppdaterer loadItems ()
og saveItems ()
metoder, vi skal implementere en hjelpemetode som heter pathForItems ()
. Metoden er privat og returnerer en bane, plasseringen av elementene i dokumentmappen.
privat func pathForItems () -> String guard la documentsDirectory = NSSearchPathForDirectoriesInDomains (.documentDirectory, .userDomainMask, true) .first, la url = URL (streng: documentsDirectory) ellers fatalError ("Dokumentsøk ikke funnet") returner url. appendingPathComponent ("items"). path
Vi henter først banen til dokumentkatalogen i programmets sandkasse ved å påkalle NSSearchPathForDirectoriesInDomains (_: _: _ :)
. Fordi denne metoden returnerer en rekke strenger, tar vi det første elementet.
Legg merke til at vi bruker en vakt
uttalelse for å sikre verdien returnert av NSSearchPathForDirectoriesInDomains (_: _: _ :)
er gyldig. Vi kaster en dødelig feil hvis denne operasjonen mislykkes. Dette avslutter umiddelbart søknaden. Hvorfor gjør vi dette? Hvis operativsystemet ikke kan gi oss veien til dokumentmappen, har vi større problemer å bekymre seg for.
Verdien vi kommer tilbake fra pathForItems ()
er sammensatt av banen til dokumentmappen med strengen "elementer"
lagt til det.
loadItems ()
MetodeLastItems metode endrer seg ganske mye. Vi lagrer først resultatet av pathForItems ()
i en konstant, sti
. Vi unarchive deretter objektet arkivert på den banen og nedkastet den til et valgfritt utvalg av Å gjøre
forekomster. Vi bruker valgfri binding for å pakke ut valgfri og tildele den til en konstant, elementer
. I hvis
klausul, tilordner vi verdien som er lagret i elementer
til elementer
eiendom.
private func loadItems () let path = pathForItems () hvis la ting = NSKeyedUnarchiver.unarchiveObject (withFile: path) som? [ToDo] self.items = items
saveItems ()
MetodeDe saveItems ()
Metoden er kort og enkel. Vi lagrer resultatet av pathForItems ()
i en konstant, sti
, og påkalle archiveRootObject (_: toFile :)
på NSKeyedArchiver
, passerer i elementer
eiendom og sti
. Vi skriver ut resultatet av operasjonen til konsollen.
privat func saveItems () let path = pathForItems () hvis NSKeyedArchiver.archiveRootObject (self.items, toFile: path) skriv ut ("vellykket lagret") else print ("Lagring mislyktes")
La oss avslutte med den morsomme delen, å slette koden. Begynn med å fjerne checkedItems
eiendom øverst siden vi ikke lenger trenger det. Som et resultat kan vi også fjerne loadCheckedItems ()
og saveCheckedItems ()
metoder, og enhver henvisning til disse metodene i ViewController
klasse.
Bygg og kjør programmet for å se om alt fortsatt fungerer. Datamodellen gjør programmets kode mye enklere og mer pålitelig. Takk til Å gjøre
klassen, administrerer elementene i vår liste mye, er nå enklere og mindre feilgodt.
I denne leksjonen reflekterte vi datamodellen i søknaden vår. Du lærte mer om objektorientert programmering og arv. Instansinitialisering er et viktig konsept i Swift, så sørg for at du forstår hva vi har dekket i denne leksjonen. Du kan lese mer om initialisering og initieringsdelegasjon i The Swift Programming Language.
I mellomtiden kan du sjekke ut noen av våre andre kurs og opplæringsprogrammer om swift språk iOS-utvikling!