Ce este o defecțiune de bază a datelor?

Defectele sunt o componentă esențială a datelor de bază. Chiar dacă termenul sună amenințător, defectele sunt inerente ciclului de viață al unei înregistrări de date de bază. În acest tutorial, veți afla ce defecte sunt, cum să le gestionați și cum să depanați problemele legate de defecțiuni.

Cerințe preliminare

Datele de bază sunt un subiect avansat, așa că am să presupun că deja cunoașteți dezvoltarea Xcode și iOS. Chiar dacă voi folosi limbajul de programare Swift pentru acest tutorial, totul din acest tutorial este de asemenea aplicabil pentru Obiectiv-C.

Wat este o defecțiune?

Datele de bază sunt foarte bune la ceea ce face datorită muncii aspre a echipei Core Data a Apple. Datele de bază sunt foarte optimizate pentru a menține amprenta de memorie scăzută fără a sacrifica performanța. Defecțiunea este una dintre tehnicile de bază ale datelor de bază pentru a consuma cât mai puțină memorie posibilă.

Defecțiunea nu este unică pentru datele de bază. O tehnică similară este folosită în multe alte cadre, cum ar fi Ember și Ruby on Rails. Ideea este simplă, doar încărcați datele când este necesar. Pentru a rezolva problemele, Core Data face un pic de magie sub capotă, creând subclase personalizate la momentul compilării care reprezintă defectele. Să vedem cum funcționează acest lucru cu un exemplu.

Am creat o aplicație simplă pentru a lucra cu. Descărcați proiectul Xcode de la GitHub și deschideți-l în Xcode. Proiectul utilizează Swift 2.1, ceea ce înseamnă că aveți nevoie de Xcode 7.1 sau mai mare pentru a satisface compilatorul.

Modelul de date conține două entități, Listă și Articol. O listă poate avea zero sau mai multe elemente și un element este întotdeauna legat de o listă. Este un exemplu clasic de relație one-to-many.

Rulați aplicația și introduceți stocarea persistentă, o bază de date SQLite, cu unele date, creând câteva liste și articole. Renunțați la aplicație când ați terminat.

Erori de ardere

Ar trebui să aveți acum o aplicație cu unele date. Să vedem cum funcționează defectele în practică prin adăugarea unor declarații de tipărire. Deschis ListsViewController.swift și căutați prepareForSegue (_: expeditor :). În această metodă, preluăm lista pe care utilizatorul a selectat-o ​​în tabelul de vizualizare. Dezarhivați instrucțiunile de imprimare din prepareForSegue (_: expeditor :) după cum se arată în implementarea de mai jos.

override func prepareForSegue (segue: UIStoryboardSegue, expeditor: AnyObject?) if segue.identifier == SegueListViewController pază let indexPath = tableView.indexPathForSelectedRow else return // Fetch List let = self.fetchedResultsController.objectAtIndexPath (indexPath) ca! Tipăriți listă ("1: \ (listă)") dacă tipăriți ("3: \ (items.count)") ("item:" (item)) dacă elementul = item.anyObject () print ("5: \ (item)  print ("8: \ (listă)") // Fetch Controller de vizualizare destinație let listViewController = segue.destinationViewController as! ListViewController // Configurează controlerul de vizualizare listViewController.list = list

Rulați aplicația și atingeți una din listele din controlerul de vizualizare a listelor. Exemplul este interesant numai dacă atingeți o listă care are asociat unul sau mai multe elemente. Să examinăm pas cu pas ieșirea declarațiilor de imprimare.

1:  (entitate: Listă; id: 0xd0000000001c0000  ; date: items = ""; nume =" lista 6 ";)

Primul lucru pe care trebuie să-l notează este numele clasei, Faulting.List. Asta ne așteptăm de la numirea modulului faulting și clasa este numită Listă. În Swift, numele clasei complete a unei clase este alcătuit din numele modulului și numele clasei.

De asemenea, ieșirea din consola arată numele entității, Listă, și un dicționar de date. Nume atributul este setat la Lista 6 in timp ce articole atributul este marcat ca a vina relației. Acesta este primul tip de defecțiune din Core Data. Datele de bază înțeleg că nu este nevoie să încărcați relația. În schimb, aceasta marchează relația ca o vină. A doua declarație de tipărire confirmă acest lucru după cum puteți vedea mai jos.

2: Avertisment de relaționare "obiecte" pe obiectul gestionat (0x154e5d0b0)  (entitate: Listă; id: 0xd0000000001c0000  ; date: items = ""; nume =" lista 6 ";)

Cu toate acestea, cea de-a treia declarație de tipărire este mai interesantă. Se tipărește numărul de articole asociate listei.

3: 2

Datele de bază pot face acest lucru numai prin tragerea la sorți a relației. Se solicită stocarea persistentă pentru elementele asociate cu lista. Aceasta, totuși, este doar o parte a povestirii, așa cum este ilustrat în declarația a patra.

4: Relația "elemente" pe obiectul gestionat (0x154e5d0b0)  (entitate: Listă; id: 0xd0000000001c0000  ; date: items = ("0xd000000000540002 "," 0xd000000000580002 "); nume =" Listă 6 ";) cu obiecte (  (entitate: element; id: 0xd000000000540002  ; date: ),  (entitate: element; id: 0xd000000000580002  ; date: ))

Nu mai vedem o vină de relație, dar încă vedem o greșeală. Despre ce e vorba? Datele principale pot da doar numărul de elemente pentru listă prin tragere sau rezolvare a erorilor relației. Cu toate acestea, acest lucru nu înseamnă că datele de bază rezolvă elementele relației. Ieșirea din consola confirmă acest lucru.

Putem vedea că înregistrările pentru elementele de acolo sunt, inclusiv identificatorul Core Data, utilizat intern. Dicționarul de date, cu toate acestea, este marcat ca o eroare. Din nou, datele de bază ne oferă doar ceea ce cerem. Din fericire, detaliile cu nisip fin sunt preluate de Core Data.

Să săpăm puțin mai adânc și să aducem unul din elementele din listă. Facem asta prin chemare anyObject () pe articole obiect. Imprimăm elementul rezultat în cea de-a cincea instrucțiune de tipărire.

5:  (entitate: element; id: 0xd000000000540002  ; date: )

Rezultatul nu trebuie să vă surprindă. Rezultatul confirmă faptul că avem de-a face cu un Articol entitate. Nu este surprinzător că dicționarul de date este încă marcat ca o eroare. În a șasea declarație de tipărire, tipărim Nume atributul elementului.

6: Punctul 0

Pentru că cerem valoarea unui atribut al înregistrării, Core Data declanșează defecțiunea pentru a ne oferi această valoare. Realizează datele pentru element și umple dicționarul de date. Cea de-a șaptea declarație de tipărire confirmă aceste constatări.

7:  (entitate: element; id: 0xd000000000540002  ; date: list = "0xd0000000001c0000 "; nume =" element 0 ";)

Dicționarul de date conține Nume atributul, precum și listă relaţie. Cea de-a opta și ultima declarație de imprimare arată că relația de eroare a listă obiect este rezolvată.

8:  (entitate: Listă; id: 0xd0000000001c0000  ; date: items = ("0xd000000000540002 "," 0xd000000000580002 "); nume =" lista 6 ";)

Imposibil de îndeplinit o greșeală

Am decis să scriu acest articol pentru a explica o problemă în care mulți dezvoltatori care folosesc datele de bază au intrat într-un punct sau altul, tragând o greșeală care nu poate fi îndeplinită. Problema este simplă. Să presupunem că aveți o listă cu un număr de elemente și, la un moment dat, utilizatorul șterge lista. Ce se întâmplă cu articolele din lista respectivă? Ce se întâmplă dacă Core Data încearcă să declanșeze listă relația dintre unul dintre elementele care au aparținut acestei liste? Să aflăm.

Să revedem proiectul descărcat de la GitHub. Deschis Faulting.xcdatamodeld, modelul de date al proiectului. Selectează articole relația dintre Listă entitate și deschideți Inspector model de date pe dreapta. Ce ne interesează este Ștergeți regula, care este setat în prezent Cascadă. Aceasta înseamnă că fiecare element al listei este șters atunci când lista este ștearsă. Acest lucru are sens deoarece nu vrem să abandonăm elemente care nu sunt asociate cu o listă.

Selectează listă relația dintre Articol entitate și deschideți Inspector model de date.  Ștergeți regula din această relație este setată la Anula. Aceasta înseamnă că destinația relației, obiectul listă, este setată la nulă atunci când înregistrarea de destinație, elementul, este șters. Aceasta este regula implicită de ștergere pentru o relație.

Rulați aplicația, creați câteva liste și elemente și atingeți Articole din stânga sus pentru a afișa fiecare element pe care l-ați creat. Atingeți Anulare în partea stângă sus, ștergeți una dintre liste și apăsați pe Articole butonul din nou pentru a vedea ce sa schimbat. După cum era de așteptat, elementele asociate cu lista eliminată au fost, de asemenea, șterse de către datele de bază. Aceasta nu este magie. Datele de bază execută pur și simplu regulile de ștergere definite în modelul de date.

Putem concluziona că relațiile sunt configurate corect pentru acest tip de aplicație. Dar ce se întâmplă dacă nu configurăm corect regulile de ștergere. Deschideți modelul de date și setați regula de ștergere a ambelor relații, articole și listă, la Fara actiune. Lansați din nou aplicația și creați câteva liste și articole. Dacă ștergeți o listă și atingeți Articole butonul din stânga sus pentru a vedea fiecare element din magazinul persistent, elementele asociate cu lista eliminată ar trebui să fie acolo. Acestea nu au fost șterse, chiar dacă lista din care fac parte a fost eliminată.

Atingeți una dintre liste și vedeți ce se întâmplă. Dacă rulați aplicația pe iOS 8, atunci aplicația se va prăbuși din cauza unei excepții neobișnuite. Dacă rulați aplicația pe iOS 9, veți vedea o eroare numai în consola. Nu te zgâria capul și să ne gândim la asta împreună.

Obiect inaccesibil

Chiar dacă aplicația se blochează pe iOS 8, ieșirea pe care o vedem în consola este clară și la limită.

Eroare [7189: 2427906] *** Terminarea aplicației din cauza excepției neobișnuite "NSObjectInaccessibleException", motiv: "CoreData nu a putut să îndeplinească o eroare pentru" 0x175b2ba0 "

Primul lucru pe care trebuie să-l observați este că aplicația sa prăbușit din cauza unei excepții neobosite. Ceea ce este mai important pentru discuția noastră, totuși, este motivul pentru care a fost aruncată excepția. Datele de bază ne spun că nu a putut să-și îndeplinească o vină pentru o relație. Relația cu datele de bază se referă la: listă a elementului pe care l-am lovit în vizualizarea tabelului.

Dacă te uiți la punerea în aplicare a tableView (tableView: didSelectRowAtIndexPath :), atunci veți înțelege de ce datele de bază au aruncat o excepție. În această metodă, preluăm elementul pe care utilizatorul la apăsat și tipărește numele listei la consola.

func tableView (tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) tableView.deselectRowAtIndexPath (indexPath, animat: true) dacă let item = self.fetchedResultsController.objectAtIndexPath (indexPath) ca? Element print (listă de listă. Nume.)

Deoarece listă relația este o eroare, Core Data trebuie să declanșeze defecțiunea pentru ao rezolva. Aceasta înseamnă că datele de bază solicită stocarea persistentă pentru înregistrarea listei. Problema este că înregistrarea nu se mai află în magazinul persistent, de aceea a fost aruncată o excepție.

Ștergeți defectele inaccesibile

Până la iOS 9, aceasta a fost întotdeauna comportamentul implicit. Începând cu iOS 9, datele de bază nu mai aruncă o excepție. În schimb, înregistrează un mesaj criptic la consola. În ciuda faptului că mesajul este neclar, acesta conține încă motivul problemei.

Eroare [2806: 1306995] CoreData: avertisment: Un delegat NSManagedObjectContext depășește comportamentul de gestionare a erorilor pentru a șterge în tăcere obiectul cu ID '0xd000000000140000 "și înlocuiți zero / 0 pentru toate valorile proprietății în loc de aruncare.

În esență, datele de bază nu mai aruncă o excepție atunci când nu pot să-și îndeplinească o greșeală. Vestea oarecum bună este că aplicația dvs. nu se mai prăbușește dacă nu faceți excepția.

Motivul pentru care nu aruncați o excepție pe iOS 9 se datorează introducerii unei noi proprietăți, shouldDeleteInaccessibleFaults, și o nouă metodă, shouldHandleInaccessibleFault (_: forObjectID: triggeredByProperty :), pe NSManagedObjectContext clasă. Nu voi acoperi aceste adăugiri în acest tutorial.

Ce trebuie să vă amintiți este că iOS 9, în mod implicit, stabilește shouldDeleteInaccessibleFaults la Adevărat. Acest lucru înseamnă că un obiect gestionat inaccesibil este marcat ca șters și proprietățile acestuia sunt zero. Dacă shouldDeleteInaccessibleFaults este setat sa fals, Datele principale revin la vechiul comportament prin aruncarea unei excepții.

Desigur, shouldDeleteInaccessibleFaults proprietatea merge mână în mână cu shouldHandleInaccessibleFault (_: forObjectID: triggeredByProperty :). Dacă înlocuiți această metodă, puteți gestiona obiectele inaccesibile mai elegant.

Manipularea defectelor

Când întâlniți o excepție datorită faptului că datele de bază nu reușesc să îndeplinească o eroare, vă puteți gândi că datele de bază sunt puțin agresive. Această reacție este destul de frecventă pentru oamenii noi în cadrul. Adevărul este că dezvoltatorul este de vină. Datele de bază au fost concepute cu un set specific de obiective în minte, iar defecțiunile reprezintă o componentă esențială pentru realizarea acestor obiective.

În ciuda numelui lor, greșelile trebuie întotdeauna îndeplinite. Dacă o eroare nu poate fi îndeplinită, atunci înseamnă că modelul de date nu este configurat corect sau că aplicația nu respectă regulile stabilite de cadrul Core Data.

În Ghidul de bază de programare a datelor, Apple afișează o serie de scenarii care pot duce la defecțiuni care nu mai pot fi îndeplinite. Cele mai frecvente scenarii sunt relațiile configurate incorect (ștergerea regulilor) și ștergerea unui obiect gestionat în timp ce aplicația are încă o referință puternică la obiectul gestionat.

Fi avertizat, există și alte scenarii posibile. Din experiența mea, dezvoltatorii se confruntă adesea cu probleme similare datorită unei probleme cu stivele de date de bază. De exemplu, dacă aplicația păstrează o referință puternică la un obiect gestionat și, la un moment dat, alocă contextul gestionat al obiectului gestionat, atunci datele de bază nu mai pot îndeplini orice defecțiuni pentru obiectul gestionat. Sper că înțelegeți că acest lucru nu ar trebui să se întâmple dacă jucați după regulile Core Data.

Concluzie

Defectele de bază ale datelor sunt utile și sunt o componentă cheie a cadrului de persistență al Apple. Sper că acest articol v-a învățat cum să rezolvați greșelile și unde să căutați dacă vă confruntați cu greșeli care nu pot fi îndeplinite. Dacă aveți întrebări, nu ezitați să le lăsați în comentariile de mai jos sau să mă contactați pe Twitter.

Cod