Cu câțiva ani în urmă, când eram încă un angajat într-o consultanță mobilă, am lucrat la o aplicație pentru o mare bancă de investiții. Companiile mari, în special băncile, au de obicei procese pentru a se asigura că software-ul lor este sigur, robust și ușor de întreținut.
O parte din acest proces a implicat trimiterea codului aplicației pe care am scris-o unei terțe părți pentru examinare. Acest lucru nu ma deranjat, pentru că am crezut că codul meu era impecabil și că compania de revizuire ar spune același lucru.
Când răspunsul lor sa întors, verdictul a fost diferit decât am crezut. Deși au spus calitatea codului nu a fost rău, au subliniat faptul că codul a fost greu de întreținut și de testat (testarea unității nu a fost foarte populară în dezvoltarea iOS-ului de atunci).
I-am respins judecata, crezand ca codul meu a fost grozav si ca nu exista nici un fel de imbunatatire. Trebuie să nu înțeleagă!
Am avut hubrisul tipic pentru dezvoltatori: adesea credem că ceea ce facem este grozav, iar alții nu reușesc.
În retrospectivă m-am înșelat. Nu mai târziu, am început să citesc câteva bune practici. De atunci, problemele din codul meu au început să rămână ca un deget mare. Mi-am dat seama că, la fel ca mulți dezvoltatori iOS, m-am supărat unor capcane clasice de practici de codificare proaste.
Una dintre cele mai comune practici de dezvoltare iOS rău apare atunci când treci de stat între controlorii de vizualizare a unei aplicații. Eu însumi am căzut în această capcană în trecut.
Promovarea de stat în controlerele de vizualizare este vitală în orice aplicație iOS. Pe măsură ce utilizatorii dvs. navighează prin ecrane ale aplicației dvs. și interacționează cu aceasta, trebuie să păstrați o stare globală care urmărește toate modificările pe care utilizatorul le face asupra datelor.
Și aici evoluează majoritatea dezvoltatorilor iOS pentru soluția evidentă, dar incorectă: modelul singleton.
Modelul singleton este foarte rapid de implementat, mai ales în Swift, și funcționează bine. Trebuie doar să adăugați o variabilă statică unei clase pentru a păstra o instanță partajată a clasei în sine și ați terminat.
clasa Singleton static let shared = Singleton ()
Este ușor să accesați această instanță partajată de oriunde din cod:
permiteți singleton = Singleton.shared
Din acest motiv, mulți dezvoltatori cred că au găsit cea mai bună soluție pentru problema propagării de stat. Dar ei se înșeală.
Modelul singleton este de fapt considerat un model anti-model. Au fost multe discuții despre aceasta în comunitatea de dezvoltare. De exemplu, consultați această întrebare privind depășirea stivei.
Pe scurt, single-urile creează aceste probleme:
În acest moment, unii dezvoltatori cred că: "Ah, am o soluție mai bună. Eu voi folosi AppDelegate
in schimb".
Problema este că AppDelegate
clasa în aplicațiile iOS este accesată prin UIApplication
instanță partajată:
lasati appDelegate = UIApplication.shared.delegate
Dar instanța comună a UIApplication
este un singurton. Deci nu ai rezolvat nimic!
Soluția la această problemă este injectarea de dependență. Injectarea dependenței înseamnă că o clasă nu își recuperează sau nu își creează propriile dependențe, dar le primește din afară.
Pentru a vedea cum să folosiți injecția de dependență în aplicațiile iOS și cum poate permite partajarea de către stat, trebuie mai întâi să revizuim unul dintre modelele arhitecturale fundamentale ale aplicațiilor iOS: Model-View-Controller.
Modelul MVC, pe scurt, afirmă că există trei straturi în arhitectura unei aplicații iOS:
Reprezentarea obișnuită a modelului MVC este ceva de genul:
Problema este că această diagramă este greșită.
Acest "secret" se ascunde în ochi în câteva linii din documentația Apple:
"Se poate îmbina rolurile MVC jucate de un obiect, făcând obiectul, de exemplu, să îndeplinească atât regulatorul, cât și rolurile de vizualizare - caz în care se va numi un controler de vizualizare. În același mod, puteți, de asemenea, să aveți obiecte controler-model. "
Mulți dezvoltatori cred că controlorii de vizualizare sunt singurii controlere care există într-o aplicație iOS. Din acest motiv, multe coduri ajung să fie scrise în interiorul lor pentru lipsa unui loc mai bun. Aceasta este ceea ce îi face pe dezvoltatori să folosească singleturi atunci când au nevoie să propageze statul: se pare că este singura soluție posibilă.
Din liniile citate mai sus, este clar că putem adăuga o entitate nouă înțelegerii modelului MVC: controlerul de model. Controlerele de model se ocupă de modelul aplicației, îndeplinind rolurile pe care modelul însuși nu ar trebui să le îndeplinească. Acesta este modul în care ar trebui să arate schema de mai sus:
Exemplul perfect al utilizării unui controler de model este păstrarea stării aplicației. Modelul ar trebui să reprezinte numai datele aplicației dvs. Starea aplicației nu ar trebui să fie preocuparea acesteia.
Această stare de păstrare se termină, de obicei, în interiorul controlorilor de vizualizare, dar acum avem un loc nou și mai bun pentru al pune: un controler de model. Acest controler de model poate fi transferat apoi controlorilor de vizualizare pe măsură ce apar pe ecran prin injecția de dependență.
Am rezolvat modelul unic anti-model. Să vedem soluția în practică cu un exemplu.
Vom scrie o aplicație simplă pentru a vedea un exemplu concret despre cum funcționează acest lucru. Aplicația va afișa citatul dvs. preferat pe un singur ecran și vă va permite să editați oferta pe un al doilea ecran.
Aceasta înseamnă că aplicația noastră va avea nevoie de două controale de vizualizare, care vor trebui să partajeze statul. După ce vedeți cum funcționează această soluție, puteți extinde conceptul la aplicații de orice dimensiune și complexitate.
Pentru a începe, avem nevoie de un tip de model pentru a reprezenta datele, ceea ce este un citat în cazul nostru. Acest lucru se poate face cu un simplu struct:
struct Citat lasa text: String lasa autor: String
Apoi trebuie să creăm un controler de model care să dețină starea aplicației. Acest controler de model trebuie să fie o clasă. Acest lucru se datorează faptului că vom avea nevoie de o singură instanță pe care o vom transmite tuturor controlorilor de vizualizare. Tipurile de valori cum ar fi structurile se copiază atunci când le transmitem în jur, deci acestea nu sunt în mod clar soluția potrivită.
Toți administratorii noștri de modele, în exemplul nostru, sunt o proprietate în care pot păstra cotația curentă. Dar, desigur, în aplicațiile mai mari controlorii de model pot fi mai complexi decât acesta:
clasa ModelController var quote = Citat (text: "Două lucruri sunt infinite: universul și prostia umană și nu sunt sigur despre univers", autor: Albert Einstein)
Am atribuit o valoare implicită pentru citat proprietate, astfel încât vom avea deja ceva de afișat pe ecran când apare aplicația. Acest lucru nu este necesar și ați putea declara proprietatea ca fiind opțional inițializată zero, dacă doriți ca aplicația să se lanseze cu o stare necompletată.
Avem acum controlerul de model, care va conține starea aplicației noastre. Apoi, avem nevoie de controalele de vizualizare care vor reprezenta ecranele aplicației noastre.
În primul rând, le creăm interfețele utilizatorilor. Acesta este modul în care cei doi controlori de vizualizare se uită în interiorul tabloului de bord al aplicației.
Interfața primului controler de vizualizare este alcătuită dintr-o serie de etichete și un buton, alcătuite împreună cu constrângeri simple de aspect auto. (Puteți citi mai multe despre aspectul auto aici pe Envato Tuts +.)
Interfața celui de-al doilea controler de vizualizare este aceeași, dar are o vizualizare text pentru a edita textul citatului și un câmp de text pentru a edita autorul.
Cele două controlere de vizualizare sunt conectate printr-o simplă prezentare modală, care provine din Editați cotația buton.
Puteți explora interfața și constrângerile controlorilor de vizualizare din repo GitHub.
Acum trebuie să codificăm controlerele noastre de vizualizare. Lucrul important pe care trebuie să-l amintim aici este că trebuie să primească instanța controlerului model din exterior, prin injecția de dependență. Deci, ei trebuie să expună o proprietate în acest scop.
var modelController: ModelController!
Putem să sunăm primul nostru controller QuoteViewController
. Acest controler de vizualizare are nevoie de câteva ieșiri la etichetele pentru citat și autor în interfața sa.
clasa QuoteViewController: UIViewController @IBOutlet slab var quoteTextLabel: UILabel! @IBOutlet slab var quoteAuthorLabel: UILabel! var modelController: ModelController!
Când acest controler de vizualizare apare pe ecran, vom popula interfața sa pentru a afișa citatul curent. Am pus codul pentru a face acest lucru în controler viewWillAppear (_ :)
metodă.
clasa QuoteViewController: UIViewController @IBOutlet slab var quoteTextLabel: UILabel! @IBOutlet slab var quoteAuthorLabel: UILabel! var modelController: ModelController! suprascrie func viewWillAppear (_ animat: Bool) super.viewWillAppear (animat) quote = modelController.quote quoteTextLabel.text = quote.text quoteAuthorLabel.text = quote.author
Am fi putut pune acest cod în interiorul lui viewDidLoad ()
metodă, care este destul de comună. Problema, totuși, este asta viewDidLoad ()
se numește o singură dată când este creat controlerul de vizualizare. În aplicația noastră, trebuie să actualizăm interfața de utilizator din QuoteViewController
de fiecare dată când apare pe ecran. Acest lucru se datorează faptului că utilizatorul poate edita citatul pe al doilea ecran.
De aceea folosim viewWillAppear (_ :)
în loc de viewDidLoad ()
. În acest fel, putem actualiza interfața de utilizare a controlerului vizual de fiecare dată când apare pe ecran. Dacă doriți să aflați mai multe despre ciclul de viață al unui controler de vizualizare și despre toate metodele care sunt numite, am scris un articol detaliind toate acestea.
Acum trebuie să codificăm cel de-al doilea controler de vizualizare. O vom numi pe asta EditViewController
.
clasa EditViewController: UIViewController @IBOutlet slab var textView: UITextView! @IBOutlet slab var textField: UITextField! var modelController: ModelController! override functie viewDidLoad () super.viewDidLoad () quote = modelController.quote textView.text = quote.text textField.text = quote.author
Acest controler de vizualizare este ca cel precedent:
În acest caz, am folosit viewDidLoad ()
deoarece acest controler de vizualizare se afișează pe ecran o singură dată.
Acum trebuie să trecem statul între cele două controlere de vizualizare și să îl actualizăm atunci când utilizatorul editează cotația.
Transmitem starea aplicației în pregăti (pentru: expeditor :)
Metodă de QuoteViewController
. Această metodă este declanșată de sega conectată atunci când utilizatorul se apasă pe Editați cotația buton.
clasa QuoteViewController: UIViewController @IBOutlet slab var quoteTextLabel: UILabel! @IBOutlet slab var quoteAuthorLabel: UILabel! var modelController: ModelController! override functie viewWillAppear (_ animat: Bool) super.viewWillAppear (animat) quote = modelController.quote quoteTextLabel.text = quote.text quoteAuthorLabel.text = quote.author suprascrie func prepare (pentru segue: UIStoryboardSegue, expeditor: Any? ) dacă let editViewController = segue.destination as? EditViewController editViewController.modelController = modelController
Aici trecem mai departe instanța ModelController
care păstrează starea aplicației. Acesta este locul unde injecția de dependență pentru EditViewController
se întâmplă.
În EditViewController
, trebuie să actualizăm starea la noul citat introdus înainte de a ne întoarce la controlerul de vizualizare anterior. Putem face acest lucru într - o acțiune legată de Salvați buton:
clasa EditViewController: UIViewController @IBOutlet slab var textView: UITextView! @IBOutlet slab var textField: UITextField! var modelController: ModelController! override funcția viewDidLoad () super.viewDidLoad () quote = modelController.quote textView.text = quote.text textField.text = quote.author @IBAction func save (_ expeditor: AnyObject) lasă newQuote = Citat (text: textView.text, autor: textField.text!) modelController.quote = newQuote respins (animat: true, completare: nil)
Aproape am terminat, dar ați fi observat că încă mai lipsim ceva: QuoteViewController
trece prin ModelController
la EditViewController
prin injecție de dependență. Dar cine dă această instanță QuoteViewController
in primul loc? Amintiți-vă că atunci când utilizați injecția de dependență, un controler de vizualizare nu trebuie să creeze propriile dependențe. Acestea trebuie să vină din afară.
Dar nu există un controler de vizualizare înainte de QuoteViewController
, deoarece acesta este primul controler de vizualizare a aplicației noastre. Avem nevoie de un alt obiect pentru a crea ModelController
instanță și să o transmită acesteia QuoteViewController
.
Acest obiect este AppDelegate
. Rolul delegatului aplicației este să răspundă metodelor ciclului de viață al aplicației și să configureze aplicația în consecință. Una dintre aceste metode este aplicare (_: didFinishLaunchingWithOptions :)
, care este sunat imediat ce apare aplicația. Aici creăm instanța ModelController
și trimiteți-l la QuoteViewController
:
clasa AppDelegate: UIResponder, UIApplicationDelegate var fereastră: UIWindow? func cerere (_ cerere: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool daca permiteti quoteViewController = window? .rootViewController as? QuoteViewController quoteViewController.modelController = ModelController () return true
Aplicația noastră este acum completă. Fiecare controler de vizualizare accesează starea globală a aplicației, dar nu folosim singleturi oriunde în codul nostru.
Puteți descărca proiectul Xcode pentru această aplicație exemplu în GitHub repo tutorial.
În acest articol ați văzut cum folosirea singletletons pentru propagarea statului într-o aplicație iOS este o practică proastă. Singleturile creează o mulțime de probleme, în ciuda faptului că sunt foarte ușor de creat și de utilizat.
Am rezolvat problema examinând mai îndeaproape modelul MVC și înțelegând posibilitățile ascunse în el. Prin utilizarea controlorilor de model și a injecției de dependență am reușit să propagăm starea aplicației peste toți controlorii de vizualizare fără a folosi singletone.
Acesta este un exemplu simplu de aplicație, dar conceptul poate fi generalizat la aplicații de orice complexitate. Aceasta este cea mai bună metodă standard de propagare a stării în aplicațiile iOS. Acum o folosesc în fiecare aplicație pe care o scriu pentru clienții mei.
Câteva lucruri pe care trebuie să le țineți cont atunci când extindeți conceptul la aplicații mai mari:
Rămâi acordat pentru mai multe sfaturi și bune practici pentru dezvoltarea aplicațiilor iOS!