Înțelegerea diferențelor dintre modelul de date de bază și obiectele gestionate

publicat de donnywals pe Octombrie 5, 2020

este posibil să fi observat că atunci când Xcode generează NSManagedObject clase bazate pe fișierul model de date de bază, cele mai multe dintre proprietățile obiectului gestionat sunt opționale. Chiar dacă le-ați făcut necesare în editorul de modele, Xcode va genera un obiect gestionat în care majoritatea proprietăților sunt opționale.

în acest articol vom explora acest fenomen și de ce se întâmplă.

explorarea subclaselor NSManagedObject generate

când construiți un proiect care utilizează generarea automată a codului Xcode pentru modelele de date de bază, subclasele NSManagedObject sunt generate atunci când construiți proiectul. Aceste clase sunt scrise dosarul de date derivat al proiectului dvs. și nu ar trebui să le modificați direct. Modelele generate de Xcode vor avea proprietăți opționale pentru unele dintre proprietățile pe care le-ați adăugat entității dvs., indiferent dacă ați făcut proprietatea opțională în editorul de modele.

deși acest lucru ar putea suna ciudat la început, de fapt nu este atât de ciudat. Aruncați o privire la următoarea subclasă NSManagedObject :

extension ToDoItem { @nonobjc public class func fetchRequest() -> NSFetchRequest<ToDoItem> { return NSFetchRequest<ToDoItem>(entityName: "ToDoItem") } @NSManaged public var completed: Bool @NSManaged public var label: String?}

una dintre cele două proprietăți pentru ToDoItem este opțională, chiar dacă ambele sunt necesare în editorul de modele. Când creez o instanță a acestui ToDoItem, aș folosi următorul cod:

let item = ToDoItem(context: managedObjectContext)

inițializatorul unui obiect gestionat ia un context de obiect gestionat. Aceasta înseamnă că nu atribuie o valoare proprietăților gestionate în timpul inițializării ToDoItem. Imprimarea valorii atât pentru label și completed proprietăți randamentele și rezultat interesant:

print(item.label) // nilprint(item.completed) // false

în timp ce label este nil așa cum era de așteptat, datele de bază au atribuit o valoare implicită de false proprietății completedceea ce are sens, deoarece Xcode a generat o proprietate non-opțională pentru completed. Să facem un pas mai departe și să aruncăm o privire la următorul cod:

let item = ToDoItem(context: managedObjectContext)item.label = "Hello, item"print(item.label) // "Hello, item"print(item.completed) // falsedo { try managedObjectContext.save()} catch { print(error)}

când executați acest cod, veți găsi că produce următoarea ieșire:

Optional("Hello, item")falseError Domain=NSCocoaErrorDomain Code=1570 "completed is a required value." UserInfo={NSValidationErrorObject=<CoreDataExample.ToDoItem: 0x60000131c910> (entity: ToDoItem; id: 0x600003079900 <x-coredata:///ToDoItem/t1FABF4F1-0EF4-4CE8-863C-A815AA5C42FF2>; data: { completed = nil; label = "Hello, item";}), NSValidationErrorKey=completed, NSLocalizedDescription=completed is a required value.}

această eroare spune clar completed is a required value. ceea ce implică faptul că completed nu este setat, iar obiectul gestionat tipărit afișat alături de mesajul de eroare arată, de asemenea, că completedeste nil. Deci, deși există un fel de valoare implicită prezentă pentru completed, nu este considerată non-nil până când nu este atribuită în mod explicit.

pentru a înțelege ce se întâmplă, putem atribui o valoare completed și să aruncăm o privire la descrierea tipărită pentru item din nou:

let item = ToDoItem(context: managedObjectContext)item.label = "Hello, item"print(item.completed)print(item)

acest cod produce următoarea ieșire:

false<CoreDataExample.ToDoItem: 0x6000038749b0> (entity: ToDoItem; id: 0x600001b576e0 <x-coredata:///ToDoItem/tD27C9C9D-A676-4280-9D7C-A1E154B2AD752>; data: { completed = 0; label = "Hello, item";})

acest lucru este destul de interesant, nu-i așa?

proprietatea completed este definită ca Bool, dar este tipărită ca număr. Putem găsi motivul pentru acest lucru în magazinul SQLite de bază. Cel mai simplu mod de a explora fișierul SQLite al magazinului dvs. de date de bază este prin trecerea -com.apple.CoreData.SQLDebug 1 ca argument de lansare a aplicației dvs. și deschiderea fișierului SQLite la care se conectează datele de bază într-un explorator SQLite, cum ar fi browserul de baze de date SQLite.

sfat:
Aflați mai multe despre argumentele de lansare a datelor de bază în acest post.

când vă uitați la definiția schemei pentru ZTODOITEM veți descoperi că folosește INTEGERca tip pentru ZCOMPLETED. Aceasta înseamnă că proprietatea completed este stocată ca un număr întreg în magazinul SQLite subiacent. Motivul pentru care completed este stocat ca INTEGER este simplu. SQLite nu are un tip BOOLEAN și folosește o valoare INTEGER de 0 pentru a reprezenta false și 1 pentru a reprezenta true în schimb.

data pe care îl vedeți imprimat când imprimați instanța de obiect gestionat nu este valoarea pentru proprietatea completed, este valoarea pentru completed care va fi scrisă în magazinul SQLite.

sunt două lucruri de învățat din această secțiune.

în primul rând, acum știți că există o nepotrivire între opționalitatea modelului de date de bază definit și obiectele gestionate generate. Un String non-opțional este reprezentat ca String opțional în modelul dvs. generat, în timp ce un Bool non-opțional este reprezentat ca Bool non-opțional în modelul dvs. generat.

în al doilea rând, ați învățat că există o diferență între modul în care o valoare este reprezentată în modelul de obiect gestionat față de modul în care este reprezentată în magazinul SQLite de bază. Pentru a vedea ce valori sunt utilizate pentru a scrie instanța de obiect gestionat în spațiul de stocare subiacent, puteți imprima obiectul gestionat și citi câmpul data din ieșirea imprimată.

lecția principală aici este că modelul dvs. de date de bază din editorul de modele și subclasele de obiecte gestionate nu reprezintă date în același mod. Opțional în modelul de date de bază nu înseamnă întotdeauna opțional în subclasa de obiecte gestionate și invers. O valoare non-opțională în modelul de date de bază poate fi reprezentată ca o valoare opțională în subclasa de obiecte gestionate. Datele de bază vor valida obiectul gestionat în raport cu modelul său de obiect gestionat atunci când încercați să îl scrieți în magazinul persistent și să aruncați erori dacă întâmpină erori de validare.

deci, de ce există această nepotrivire? Nu ar fi mult mai ușor dacă modelul de obiect gestionat și subclasele de obiecte gestionate ar avea o mapare directă?

înțelegerea nepotrivirii dintre obiectele gestionate și modelul de date de bază

o mare parte din motivul pentru care există o nepotrivire între obiectele gestionate și modelul pe care l-ați definit în editorul de modele provine din rădăcinile Objective-C ale datelor de bază.

deoarece Objective-C nu se ocupă deloc de Optional, nu există întotdeauna o mapare bună de la definiția modelului la codul Swift. Deseori, modul în care lucrările de cartografiere pare oarecum arbitraty. De exemplu, Optional<String> și Optional<Bool> ambele nu pot fi reprezentate ca un tip în Objective-C pentru simplul motiv că Optional nu există în Objective-C. Cu toate acestea, Swift și Objective-C pot interacționa între ele și Optional<String> pot fi conectate la un NSString automat. Din păcate, Optional<Bool> nu poate fi mapat la nimic în Objective-C automat ca Xcode vă va spune atunci când încercați să definiți o proprietate @NSManaged ca Bool?.

dacă nu ați lucrat niciodată cu Objective-C, S-ar putea să vi se pară foarte ciudat că nu există niciun concept de Optional. Cum au folosit oamenii proprietăți opționale în datele de bază înainte de Swift? Și ce se întâmplă când ceva ar trebui să fie nil în Objective-C?

în Objective-C este perfect în regulă ca orice valoare să fie nil, chiar și atunci când nu vă așteptați. Și din moment ce datele de bază își au rădăcinile în Objective-C, o parte din această moștenire se transmite claselor Swift generate într-o manieră uneori mai puțin ideală.

cel mai important aspect aici nu este modul în care funcționează Objective-C sau modul în care Xcode generează exact codul. În schimb, vreau să vă amintiți că tipurile și configurația din definiția modelului de date de bază nu (trebuie) să se potrivească tipurilor din subclasa obiectului gestionat (generat).

în rezumat

în articolul din această săptămână ați învățat multe despre modul în care subclasele de obiecte gestionate și definiția modelului de date de bază nu se aliniază întotdeauna așa cum v-ați aștepta. Ați văzut că uneori o proprietate non-opțională din editorul de modele poate ajunge ca opțională în subclasa de obiecte gestionate generate, iar alteori ajunge ca o proprietate non-opțională cu o valoare implicită, chiar dacă nu ați atribuit singur o valoare implicită.

de asemenea, ați văzut că dacă o valoare implicită este prezentă pe o instanță de obiect gestionat, aceasta nu înseamnă că valoarea este de fapt prezentă în momentul în care salvați obiectul gestionat, cu excepția cazului în care ați definit în mod explicit o valoare implicită în editorul modelului de date de bază.

deși acest lucru este cu siguranță confuz și nefericit, datele de bază sunt destul de bune pentru a vă spune ce este greșit în erorile pe care le aruncă în timp ce salvează un obiect gestionat. De asemenea, este posibil să inspectați valorile pe care datele de bază vor încerca să le stocheze imprimând instanța obiectului gestionat și inspectând atributul data.

într-o notă personală, sper că comportamentul pe care l-am descris în articolul din această săptămână este abordat într-o actualizare viitoare a datelor de bază care îl face mai rapid prietenos în cazul în care subclasele de obiecte gestionate au o mapare mai apropiată, posibil directă, la modelul de date de bază definit într-un editor de modele. Dar până atunci, este important să înțelegeți că editorul de modele și subclasele de obiecte gestionate nu reprezintă modelul dvs. în același mod și că acest lucru este cel puțin parțial legat de rădăcinile Objective-C ale datelor de bază.

dacă aveți întrebări, corecții sau feedback despre acest post, vă rugăm să-mi spuneți pe Twitter. Această postare face parte din unele cercetări, explorări și pregătiri pe care le fac pentru o carte despre datele de bază la care lucrez. Pentru actualizări despre această carte asigurați-vă că urmați-mă pe Twitter. În prezent intenționez să lansez cartea la sfârșitul anului 2020.

fii la curent cu newsletter-ul meu săptămânal

combinare practică

aflați tot ce trebuie să știți despre combinare și cum îl puteți folosi în proiectele dvs. cu noua mea carte combinare practică. Vei primi treisprezece capitole, un loc de joacă și o mână de proiecte de probă pentru a vă ajuta să te ridici și să fie difuzate cu combina cât mai curând posibil.

cartea este disponibilă ca descărcare digitală pentru doar 29,99 USD!

Ia Practice Combina

Lasă un răspuns

Adresa ta de email nu va fi publicată.