În lecția anterioară despre Swift From Scratch, am creat o aplicație funcțională funcțională. Modelul de date ar putea folosi ceva dragoste. În această lecție finală, vom reface modelul de date prin implementarea unei clase de model personalizate.
Modelul de date pe care urmează să îl implementăm include două clase: a Sarcină
clasa și a A face
clasa care moștenește de la Sarcină
clasă. În timp ce noi creăm și implementăm aceste clase de modele, vom continua să explorăm programarea orientată obiect în Swift. În această lecție, vom mări imaginea inițializării instanțelor de clasă și a rolului pe care îl joacă mostenirea în timpul inițializării.
Sarcină
ClasăSă începem cu punerea în aplicare a Sarcină
clasă. Creați un nou fișier Swift selectând Nou> Fișier ... din Xcode Fişier meniul. Alege Swift File de la iOS> Sursă secțiune. Denumiți fișierul Task.swift și lovit Crea.
Implementarea de bază este scurtă și simplă. Sarcină
clasa moșteni de la NSObject
, definită în fundație cadru și are o proprietate variabilă Nume
de tip Şir
. Clasa definește două inițializatoare, init ()
și init (nume :)
. Există câteva detalii care s-ar putea să vă lase în urmă, așa că permiteți-mi să vă explic ce se întâmplă.
(nume: "New Task") init (nume: String) auto.name = nume
Deoarece init ()
metoda este, de asemenea, definită în NSObject
, trebuie să prefixăm inițializatorul cu trece peste
cuvinte cheie. Am abordat metode de prioritate din această serie. În init ()
metoda, invocăm init (nume :)
metodă, care trece "Sarcina noua"
ca valoare pentru Nume
parametru.
init (nume :)
este un alt inițializator, acceptând un singur parametru Nume
de tip Şir
. În acest inițializator, valoarea lui Nume
parametru este atribuit Nume
proprietate. Acest lucru este destul de ușor să înțelegeți. Dreapta?
Ce e cu comoditate
prefixarea cuvintelor cheie init ()
metodă? Clasele pot avea două tipuri de inițializatoare, desemnat inițializatorii și comoditate initializatori. Inițializatorii de confidențialitate sunt prefixați cu comoditate
cuvânt cheie, ceea ce implică acest lucru init (nume :)
este un inițializator desemnat. De ce este asta? Care este diferența dintre inițialele desemnate și conveniența?
Inițializatori desemnați inițializați complet o instanță a unei clase, ceea ce înseamnă că fiecare proprietate a instanței are o valoare inițială după inițializare. Privind la Sarcină
clasa, de exemplu, vedem că Nume
proprietatea este setată cu valoarea lui Nume
parametru al init (nume :)
initializare. Rezultatul după inițializare este complet inițializat Sarcină
instanță.
Inițializatoare de conveniență, totuși, să se bazeze pe un inițializator desemnat pentru a crea o instanță complet inițializată a clasei. De aceea init ()
inițializator al Sarcină
clasa invocă init (nume :)
inițializator în implementarea sa. Acest lucru este denumit inițiator. init ()
inițializatorul inițiază delegarea inițializării la un inițializator desemnat pentru a crea o instanță complet inițializată a Sarcină
clasă.
Inițializatorii de confort sunt opțional. Nu fiecare clasă are un inițializator de confort. Sunt necesare inițializoare desemnate și o clasă trebuie să aibă cel puțin un inițializator desemnat pentru a crea o instanță pe deplin inițializată.
NSCoding
ProtocolPunerea în aplicare a directivei Sarcină
clasa nu este completă, totuși. Mai târziu, în această lecție, vom scrie o serie de A face
instanțe pe disc. Acest lucru este posibil numai dacă instanțele A face
clasa poate fi codificată și decodificată.
Nu vă faceți griji, totuși, asta nu este știința rachetelor. Trebuie doar să facem Sarcină
și A face
clasele sunt conforme cu NSCoding
protocol. De aceea Sarcină
clasa moșteni de la NSObject
clasa de la NSCoding
protocolul poate fi implementat numai de clase care moștenesc - direct sau indirect - de la NSObject
. Ca NSObject
clasa, NSCoding
protocol este definit în fundație cadru.
Adoptarea unui protocol este un lucru pe care deja l-am abordat în această serie, dar sunt câteva gânduri pe care vreau să le subliniez. Să începem prin a spune compilatorului că Sarcină
clasa este conformă cu NSCoding
protocol.
importă Clasa Fundației Task: NSObject, NSCoding var name: String ...
Apoi, trebuie să implementăm cele două metode declarate în NSCoding
protocol, init? (coder :)
și encode (cu :)
. Implementarea este simplă dacă sunteți familiarizat cu NSCoding
protocol.
importă Clasa Fundației Task: NSObject, NSCoding var nume: String @objc necesar init? (coder aDecoder: NSCoder) name = aDecoder.decodeObject (pentruKey: "nume") ca! String @objc func encode (cu aCoder: NSCoder) aCoder.encode (nume, pentruKey: "nume") comutare override init () self.init (nume: "New Task" auto.name = nume
init? (coder :)
initializer este un inițializator desemnat care inițializează a Sarcină
instanță. Chiar dacă implementăm init? (coder :)
- metoda pentru a se conforma NSCoding
protocol, nu va trebui să invocați direct această metodă. Același lucru este valabil și pentru encode (cu :)
, care codifică o instanță a Sarcină
clasă.
necesar
prefixarea cuvintelor cheie init? (coder :)
metoda indică faptul că fiecare subclasă din Sarcină
clasa trebuie să implementeze această metodă. necesar
cuvântul cheie se aplică numai inițializatorilor, motiv pentru care nu este necesar să îl adăugăm la encode (cu :)
metodă.
Înainte de a merge mai departe, trebuie să vorbim despre @objc
atribut. Deoarece NSCoding
protocol este un protocol Obiectiv-C, conformitatea protocolului poate fi verificată numai prin adăugarea @objc
atribut. În Swift, nu există nici un fel de metode conforme protocolului sau metode opționale de protocol. Cu alte cuvinte, dacă o clasă aderă la un anumit protocol, compilatorul verifică și se așteaptă ca fiecare metodă a protocolului să fie implementată.
A face
ClasăCu Sarcină
clasa puse în aplicare, este timpul să pună în aplicare A face
clasă. Creați un nou fișier Swift și denumiți-l ToDo.swift. Să ne uităm la punerea în aplicare a A face
clasă.
importați Clasa Fundației ToDo: Sarcina var done: Bool @objc este necesară init? (coder aDecoder: NSCoder) self.done = aDecoder.decodeBool (pentruKey: "done") super.init (coder: aDecoder) (nume: String, done: Bool) self.done = done super.init (nume: String, done: Bool) : Nume)
A face
clasa moșteni de la Sarcină
clasă și declară o proprietate variabilă Terminat
de tip bool
. În plus față de cele două metode necesare ale NSCoding
protocol pe care îl moștenește de la Sarcină
clasa, declara de asemenea un inițializator desemnat, init (nume: :) făcut
.
Ca și în Obiectivul C, super
cuvântul cheie se referă la superclase, Sarcină
clasă în acest exemplu. Există un detaliu important care merită atenție. Înainte de a invoca init (nume :)
metoda pe superclaj, fiecare proprietate declarată de A face
clasa trebuie inițializată. Cu alte cuvinte, înainte de A face
clasa delegaților inițierea la superclase, fiecare proprietate definită de A face
clasa trebuie să aibă o valoare inițială valabilă. Puteți verifica acest lucru comutând ordinea instrucțiunilor și inspectând eroarea care apare.
Același lucru este valabil și pentru init? (coder :)
metodă. Mai întâi inițializăm Terminat
proprietatea înainte de invocare init? (coder :)
pe superclaj.
Când se ocupă de moștenire și de inițializare, există câteva reguli pe care trebuie să le țineți minte. Regula pentru inițialele desemnate este simplă.
A face
clasa, de exemplu, init? (coder :)
metoda invocă init? (coder :)
metoda superclasei sale. Aceasta este, de asemenea, menționată ca delegație.Regulile pentru inițialele de confort sunt puțin mai complexe. Există două reguli care trebuie păstrate în minte.
Sarcină
clasa, de exemplu, init ()
este un inițializator de confort și deleagă inițializarea unui alt inițializator, init (nume :)
în exemplu. Acest lucru este cunoscut sub numele de delegarea.Cu ambele clase de modele implementate, este timpul să refactor ViewController
și AddItemViewController
clase. Să începem cu cel din urmă.
AddItemViewController
AddItemViewControllerDelegate
ProtocolSingurele schimbări pe care trebuie să le facem în AddItemViewController
clasa sunt legate de AddItemViewControllerDelegate
protocol. În declarația protocolului, schimbați tipul de didAddItem
din Şir
la A face
, clasa de model pe care am implementat-o mai devreme.
protocol AddItemViewControllerDelegate func controler (_ controler: AddItemViewController, didAddItem: ToDo)
crea(_:)
AcțiuneAceasta înseamnă că trebuie, de asemenea, să actualizăm crea(_:)
acțiune în care invocăm metoda delegatului. În implementarea actualizată, vom crea o A face
instanță, transmițând-o la metoda delegatului.
@IBAction func create (_ expeditor: Oricare) if let name = textField.text // Creare item let = item ToDo (nume: name, done: false) // Notify Delegate delegate? .Controller (self, didAddItem: )
ViewController
articole
Proprietate ViewController
clasa necesită un pic mai mult de lucru. Mai întâi trebuie să schimbăm tipul de articole
proprietate la [A face]
, o serie de A face
instanțe.
obiecte var: [ToDo] = [] didSet (oldValue) lăsați hasItems = items.count> 0 tableView.isHidden =! hasItems messageLabel.isHidden = hasItems
Aceasta înseamnă, de asemenea, că trebuie să refacem câteva alte metode, cum ar fi tableView (_: cellForRowAt :)
metoda prezentată mai jos. Deoarece articole
matrice conține acum A face
instanțe, verificarea dacă un element este marcat ca făcut este mult mai simplu. Utilizăm operatorul condițional ternar al lui Swift pentru a actualiza tipul accesoriu al celulei de vizualizare a tabelului.
func tableView (_tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell // Returnați elementul lasă item = items [indexPath.row] // Dequeue Cell lasă cell = tableView.dequeueReusableCell (withIdentifier: "TableViewCell" ) // Configurați Cell cell.textLabel? .Text = item.name cell.accessoryType = item.done? .checkmark: .no celulă retur
Când utilizatorul șterge un element, trebuie doar să îl actualizăm articole
de proprietate prin eliminarea corespunzătoare A face
instanță. Acest lucru se reflectă în punerea în aplicare a directivei tableView (_: comite: forRowAt :)
metoda prezentată mai jos.
func tableView (_ tableView: UITableView, comite editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) dacă editingStyle == .delete // Actualizați elementele items.remove (la: indexPath.row) // Update Table View tableView.deleteRows : [indexPath], cu: .right) // Salvare stare saveItems ()
Actualizarea stării unui element în momentul în care utilizatorul pune la dispoziție un rând este tratat în tableView (_: didSelectRowAt :)
metodă. Punerea în aplicare a acestui UITableViewDelegate
metoda este mult mai simplă datorită A face
clasă.
func tableView (_tableView: UITableView, didSelectRowAt indexPath: IndexPath) tableView.deselectRow (la: indexPath, animat: true) // Fetch Item let = item [indexPath.row] // Update Item item.done =! făcut // Fetch Cell lasă cell = tableView.cellForRow (la: indexPath) // Actualizează celula celulei? .accessoryType = item.done? .checkmark: .none // Salvați statul saveItems ()
Corespondența A face
instanța este actualizată, iar această modificare este reflectată de vizualizarea tabelului. Pentru a salva statul, invocăm saveitems ()
in loc de saveCheckedItems ()
.
Pentru că am actualizat AddItemViewControllerDelegate
protocol, trebuie, de asemenea, să actualizăm ViewController
punerea în aplicare a acestui protocol. Schimbarea este totuși simplă. Trebuie doar să actualizăm semnătura metodei.
func controler (_ controler: AddItemViewController, didAddItem: ToDo) // Update Sursa de date items.append (didAddItem) // Save State saveItems () // Reîncarcă tabelul View tableView.reloadData animat: adevărat)
pathForItems ()
MetodăÎn loc să stocăm elementele din baza de date implicită pentru utilizatori, le vom stoca în directorul de documente al aplicației. Înainte de a actualiza loadItems ()
și saveitems ()
metode, vom implementa o metodă de ajutor numită pathForItems ()
. Metoda este privată și returnează o cale, locația elementelor din agenda documentelor.
private path pathForItems () -> String pază permite documentDirectory = NSSearchPathForDirectoriesInDomains (.documentDirectory, .userDomainMask, true) .first, permite url = URL (șir: documentsDirectory) altfel fatalError appendPathComponent ("items") calea
Mai întâi preluăm calea către directorul de documente din caseta de nisip a aplicației invocând NSSearchPathForDirectoriesInDomains (_: _: _ :)
. Deoarece această metodă returnează o serie de șiruri de caractere, luăm primul element.
Observați că folosim a pază
declarație pentru a vă asigura că valoarea returnată de NSSearchPathForDirectoriesInDomains (_: _: _ :)
este valabil. Aruncăm o eroare fatală dacă această operație nu reușește. Aceasta termină imediat aplicația. De ce facem asta? Dacă sistemul de operare nu ne poate duce calea către directorul de documente, avem probleme mai mari de îngrijorat.
Valoarea la care ne întoarcem pathForItems ()
este compus din calea către directorul de documente cu șirul „elemente“
anexat la acesta.
loadItems ()
MetodăMetoda loadItems se modifică destul de puțin. Mai întâi păstrăm rezultatul pathForItems ()
într-o constantă, cale
. Apoi dezarhivați obiectul arhivat pe acea cale și îl dezmembrați într-o gamă opțională de A face
instanțe. Utilizăm legarea opțională pentru a despacheta opțiunea și ao atribui unei constante, articole
. În dacă
clauza, atribuim valoarea stocată în articole
la articole
proprietate.
private func loadItems () let path = pathForItems () dacă permiteți elemente = NSKeyedUnarchiver.unarchiveObject (cuFile: path) ca? [ToDo] auto.items = elemente
saveitems ()
Metodă saveitems ()
metoda este scurtă și simplă. Stocăm rezultatul pathForItems ()
într-o constantă, cale
, și invoca archiveRootObject (_: toFile :)
pe NSKeyedArchiver
, trecerea în articole
proprietate și cale
. Imprimăm rezultatul operației la consola.
private () save path = pathForItems () dacă NSKeyedArchiver.archiveRootObject (auto.items, toFile: path) print ("Salvat cu succes") altceva print ("Salvarea a eșuat"
Să terminăm cu partea distractivă, ștergând codul. Începeți prin eliminarea checkedItems
proprietate în partea de sus, deoarece nu mai avem nevoie de ea. Ca rezultat, putem, de asemenea, elimina loadCheckedItems ()
și saveCheckedItems ()
metode și orice referire la aceste metode în ViewController
clasă.
Construiți și rulați aplicația pentru a vedea dacă totul funcționează. Modelul de date face ca codul aplicației să fie mult mai simplu și mai fiabil. Mulțumită A face
clasa, gestionarea elementelor din lista noastră este mult mai ușoară și mai puțin predispusă la erori.
În această lecție am reconfacturat modelul de date al aplicației noastre. Ați învățat mai multe despre programarea și moștenirea orientate pe obiecte. Inițializarea instanțelor este un concept important în Swift, așa că asigurați-vă că înțelegeți ce am abordat în această lecție. Puteți citi mai multe despre delegarea inițializării și a inițializatorului în Limba de programare Swift.
Între timp, verificați câteva dintre celelalte cursuri și tutoriale despre dezvoltarea iOS a limbajului Swift!