- sau 10 bătălie lecții învățate de CoreData
- CoreData este un cadru de gestionare a grafurilor de obiecte
- CoreData poate stoca datele în diferite formate
- Câteva definiții de termeni
- Utilizați NSPersistentContainer
- Utilizați relații
- Evitați codul stringly cât mai mult posibil
- Păstrați NSPersitentContainer.viewContext read-only
- Supravegheați acele fire de execuție
- Utilizați argumentul de lansare pentru a urmări contextul și threadingul
- Utilizați interogări agregate
- Păstrați codul legat de datele de bază în categorii
- Adoptați un model de tranzacție pentru modificările aduse datelor
- Nu salvați contextul din interiorul unei metode din categoria model
sau 10 bătălie lecții învățate de CoreData
Am folosit CoreData de-a lungul mai multor proiecte în timpul carierei mele de dezvoltator pe platforma Apple (prima dată a fost în MacOS 10.5…), iar de-a lungul anilor am învățat lucruri pe calea cea mai grea (aka: „bătându-mă cu capul de perete mult prea mult timp”). Așa că m-am gândit să vă împărtășesc învățătura mea. Să sperăm că vă voi scuti de câteva vânătăi pe frunte.
Rețineți că acesta nu este un ghid „how-to” pentru configurarea CoreData. Se dorește a fi un articol cu câteva bune practici pe care le puteți prelua pentru proiectele dumneavoastră.
Iată o prezentare rapidă a Apple: „Utilizați Core Data pentru a salva datele permanente ale aplicației dvs. pentru utilizare offline, pentru a stoca în memoria cache datele temporare și pentru a adăuga funcționalitatea de anulare a aplicației dvs. pe un singur dispozitiv.”
Pentru a oferi ceva mai multe detalii, CoreData este tehnologia Apple pentru a salva datele structurate la nivel local. Folosiți editorul de modele pentru a vă defini structura de date, la fel ca un editor de scheme de baze de date, apoi puteți utiliza acele obiecte în codul dumneavoastră pentru a vă stoca datele.
CoreData este un cadru de gestionare a grafurilor de obiecte
Ceea ce înseamnă acest lucru este că atunci când vă creați modelul, vă definiți obiectele și relațiile dintre ele. De exemplu, dacă aveți o entitate Companie și o entitate Angajat în modelul dvs. de date, aceste două entități vor fi legate de o relație care le leagă pe cele două. Probabil numită employees
pe entitatea Company
și employer
pe entitatea Employee
. Pur și simplu adăugați proprietatea de relație pe entități și asta este tot. Nu este nevoie să creați un tabel de îmbinare ca într-o bază de date.
Frumusețea CoreData în comparație cu o bază de date tradițională este că atunci când interoghezi pentru a obține compania X, vei putea obține angajații direct prin simpla accesare a company.employees
. Nu este nevoie să construiți o interogare de îmbinare sau să efectuați o nouă interogare.
CoreData poate stoca datele în diferite formate
Da, ați auzit bine, CoreData poate salva datele dvs. în diferite formate/locuri. Fiecare are avantajele și dezavantajele sale.
- Stocare binară
- Stocare SQLite
- Stocare în memorie
Puteți citi acest articol pentru a afla avantajele și dezavantajele fiecăruia. Eu folosesc întotdeauna SQLite, dar aș putea vedea cum un magazin în memorie ar fi super rapid.
Câteva definiții de termeni
NSManagedObject
: aceasta este clasa de bază care reprezintă obiectele pe care le-ați stocat în CoreData.
Fetch request: acesta este echivalentul unei interogări în lumea CoreData. Este reprezentată de clasa NSFetchedObject
.
Utilizați NSPersistentContainer
Până la iOS 10.0, configurarea stivei CoreData implica destul de mult cod boilerplate. Trebuia să vă descurcați singuri pentru a defini strategia de utilizat pentru a face față utilizării multithreaded a CoreData.
Cu NSPersistentContainer
, Apple încapsulează cea mai mare parte a codului boilerplate. Vă scutește de dezbaterea despre cum să vă configurați cel mai bine stiva pentru o performanță optimă.
Acest articol conține toate informațiile de care aveți nevoie pentru a o utiliza corect.
Utilizați relații
Menționez întotdeauna acest lucru deoarece, în două ocazii, pentru proiecte pe care le-am moștenit, modelul CoreData a fost configurat ca o bază de date. Nu erau definite relații. În schimb, a fost stocat ID-ul obiectului asociat și, astfel, a fost necesară o cerere suplimentară de preluare pentru a obține obiectul asociat. Cred că o mulțime de dezvoltatori abordează CoreData ca pe o bază de date și comit această eroare. Acest lucru sfârșește prin a face întregul proiect mult mai complex și mult mai lent.
Să urmați acest articol pentru a începe.
Evitați codul stringly cât mai mult posibil
Codul stringly se referă la codul care utilizează șiruri de caractere pentru a accesa date. Pe iOS, aveți câteva API-uri stringly: UserDefaults
, CoreData
sunt câteva care îmi vin în minte.
Dacă ne întoarcem la exemplul companiei, mai jos sunt două opțiuni pentru a crea o companie și a-i seta numele:
//stringly version to create and edit a company
let company = NSEntityDescription.insertNewObject(forEntityName: "Company", in: managedObjectContext)
company.set("my less than great corporation", forKey: "name")//non stringly version
let company = Company(in: managedObjectContext)
company.name = "My super corporation"
La prima vedere, ambele opțiuni funcționează, se compilează, nu este nimic în neregulă cu acestea…
Problema cu codul stringly este că compilatorul nu vă poate ajuta. Imaginați-vă că trebuie să accesați name
al companiei în mai multe locuri din cod și, în mod realist, veți avea nevoie de cel puțin două: unul pentru a seta șirul și unul pentru a-l citi, acum trebuie să tastați acel name
șir în două locuri. Sigur că puteți crea o variabilă pentru a stoca șirul și să folosiți acea variabilă, dar asta necesită un efort suplimentar, nu va fi tastat verificat și știm cu toții că dezvoltatorii sunt leneși…
Așa că, de obicei, rămâne acel șir în mai multe locuri. Apoi vrei să-l redenumești și trebuie să-l vânezi cu căutare și înlocuire… Dacă ratezi unul, aplicația ta se va bloca.
Iată lista de caracteristici ale compilatorului de care nu veți beneficia cu sintaxa stringly:
- autocompletare
- refactor
- Avertizare de compilator în cazul unei greșeli de ortografie
- Error de compilator dacă eliminați/redenumiți această entitate sau proprietate în modelul dvs.stringly, va trebui pur și simplu să solicitați compilatorului să genereze clase pentru entitățile dvs. (puteți urma instrucțiunile Apple în secțiunea „Select a Code Generation Option”). Aceasta este acum opțiunea implicită atunci când se creează o nouă entitate.
Păstrați NSPersitentContainer.viewContext read-only
Din motive de performanță, nu doriți să efectuați operații lungi asupra viewContext, deoarece acestea vor rula pe firul principal și, eventual, vor bloca interfața de utilizator.
Soluția este să vă obișnuiți să efectuați modificări ale obiectelor dumneavoastră numai în interiorul unui bloc pe care îl programați cu metoda
NSPersistentContainer.performBackgroundTask()
. Când vă salvați modificările în acel bloc, acestea vor fi fuzionate înapoi înNSPersitentContainer.viewContext
și vă puteți actualiza interfața de utilizare.Supravegheați acele fire de execuție
Una dintre principalele piedici cu CoreData este utilizarea acesteia într-un mod sigur pentru fire de execuție. Deși, odată ce ați înțeles conceptul, este destul de ușor.
Ce se rezumă la faptul că orice obiect de date pe care îl obțineți de la CoreData poate fi folosit doar pe firul la care este atașat
managedobjectContext
al obiectului.O modalitate de a vă asigura că folosiți firul corect pentru a accesa obiectul dvs. este să folosiți
NSManagedObjectContext.performBlockAndWait
care va asigura că codul dvs. este folosit pe firul corect.După ce vă aflați în acel bloc, va trebui să folosiți
NSManagedObject.objectID
șiNSManagedObjectContext.existingObject(withId:)
pentru a trece obiectul între fire și context.Dacă nu faceți acest lucru, cel mai probabil veți ajunge la blocaje. Imaginați-vă:
- Programați o sarcină din firul principal cu În cazul în care ceva programat din firul principal
NSManagedObjectContext.performBlockAndWait()
. În acest moment, firul principal este blocat așteptând ca acest bloc să se finalizeze. - În interiorul acestui bloc, treceți obiectul
foo
din contextul principal la metodabar()
și îl expediați către un alt fir. - Pentru a fi un bun cetățean CoreData,
bar()
va obține contextul atașat la foo și va expedia sarcina sa folosindfoo.managedObjectContext.performBlockAndWait()
- Boom, sunteți blocat, deoarece acest context se execută doar pe firul principal și firul principal așteaptă ca acesta să se completeze.
O altă convenție care ajută este trecerea obiectelor între controlorii de vizualizare numai dacă acestea există în contextul firului principal.
Utilizați argumentul de lansare pentru a urmări contextul și threadingul
Dacă urmați sfaturile de mai sus, acest lucru ar trebui să limiteze șansele ca o instanță
NSManagedObject
să fie apelată pe firul greșit. Dar acest lucru se poate întâmpla în continuare. Așadar, asigurați-vă că atunci când sunteți în dezvoltare activați aserțiunea de depanare a firelor de execuție-com.apple.CoreData.ConcurrencyDebug 1
Acest articol vă va spune exact cum să faceți acest lucru și vă va informa despre alți indicatori pe care îi puteți seta pentru mai multă depanare.
Utilizați interogări agregate
CoreData oferă
groupBy
,sum
șicount
ca funcții agregate care pot fi executate în timpul unei interogări. Configurarea acestora poate fi puțin complexă, dar merită.Alternativa este să creați o interogare pentru a obține toate obiectele care corespund criteriilor dvs. și apoi să rulați o buclă for prin toate entitățile și să faceți dvs. calculele. Mai ușor de codat, dar mult mai puțin eficient.
Când executați o cerere de preluare agregată, CoreData o traduce într-o interogare a bazei de date și execută calculele direct la nivelul bazei de date. Acest lucru o face foarte rapidă.
Acest articol este o bună introducere despre cum să o faceți.
Păstrați codul legat de datele de bază în categorii
După ce aveți subclasa
NSManagedObject
creată de Xcode, cel mai probabil veți descoperi că aveți cod care ar trebui să se afle în clasa care reprezintă obiectul dvs. (pentru calcularea valorilor, importul de date de pe server, etc.). Dar i-ați cerut lui CoreData să creeze clasele pentru dvs…Așa că va trebui să creați o categorie pentru obiectul care are nevoie de un comportament suplimentar. Puneți acolo doar codul legat de CoreData, astfel încât să îl puteți păstra curat.
Adoptați un model de tranzacție pentru modificările aduse datelor
Vă puteți modifica obiectele oricât de mult doriți, dar pentru a persista aceste modificări trebuie să apelați
NSManagedObjectContext.save()
.Vă veți trezi că uitați rapid să apelați
save()
și veți avea niște modificări atârnate în context. În acest moment, toate pariurile sunt anulate, data viitoare când veți apelasave()
, nu veți fi sigur de ceea ce confirmați.Adoptați un model de tranzacție pentru a evita să lăsați în urmă un context murdar. Pentru a face acest lucru, creați o categorie pe
NSPersistentContainer
astfel:extension NSPersistentContainer {
@objc func performBackgroundSaveTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
performBackgroundTask { (context: NSManagedObjectContext) in
block(context)
context.save()
}
}
}Asigurați-vă de acum înainte că orice modificare pe care o faceți este executată într-un bloc programat cu
performBackgroundSaveTask()
. În acest fel aveți garanția că modificările sunt salvate în momentul în care le faceți.Nu salvați contextul din interiorul unei metode din categoria model
Nu apelați niciodată
save()
din interiorul unei categorii de entități. Nu este nimic mai rău decât să apelezi o metodă asupra obiectului tău fără să-ți dai seama că aceasta va salva întregul context odată cu ea. Angajarea unor modificări pe care s-ar putea să nu doriți să le salvați. Apelarea salvării ar trebui să se facă în momentul în care se încep modificările obiectului..
- Programați o sarcină din firul principal cu În cazul în care ceva programat din firul principal