Swift From Scratch inițializare și inițializare delegare

Î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.

1. Modelul de date

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?

Inițializatori desemnați și convenționali

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 Protocol

Punerea î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.

Inițializatori și moștenire

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ă.

  • Un inițializator desemnat trebuie să invocă un inițializator desemnat din superclase. În 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.

  • Un inițializator de confort trebuie întotdeauna să invoce un alt inițializator al clasei în care este definit 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.
  • Chiar dacă un inițializator de confort nu trebuie să delege inițializarea unui inițializator desemnat, un inițializator de confort trebuie să apeleze un inițializator desemnat la un moment dat. Acest lucru este necesar pentru a inițializa complet instanța care este inițializată.

Cu ambele clase de modele implementate, este timpul să refactor ViewController și AddItemViewController clase. Să începem cu cel din urmă.

2. Refactorizare AddItemViewController

Pasul 1: Actualizați AddItemViewControllerDelegate Protocol

Singurele 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)

Pasul 2: Actualizați crea(_:) Acțiune

Aceasta î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: )

3. Refactorizare ViewController

Pasul 1: Actualizați 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

Pasul 2: Metode privind sursele de date din tabel

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 ()

Pasul 3: Metode de delegare a tabelului

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 ().

Pasul 4: Adăugați metodele de delegare a controlorului de vizualizare a elementului

Pentru că am actualizat AddItemViewControllerDelegate protocol, trebuie, de asemenea, să actualizăm ViewControllerpunerea î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)

Pasul 5: Salvați elementele

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"

Pasul 6: Curățați

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.

Concluzie

Î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!

Cod