Förstå skillnaderna mellan din Kärndatamodell och hanterade objekt

publicerad av donnywals oktober 5, 2020

du kanske har märkt att när Xcode genererar dina NSManagedObject – klasser baserat på din Core Data model-fil, är de flesta av dina hanterade objekts egenskaper valfria. Även om du har gjort dem nödvändiga i modellredigeraren genererar Xcode ett hanterat objekt där de flesta egenskaper är valfria.

i den här artikeln kommer vi att undersöka detta fenomen, och varför det händer.

utforska genererade nsmanagedobject-underklasser

när du bygger ett projekt som använder xcodes automatiska kodgenerering för Kärndatamodeller genereras dina NSManagedObject – underklasser när du bygger ditt projekt. Dessa klasser skrivs projektets härledda datamapp och du bör inte ändra dem direkt. Modellerna som genereras av Xcode har valfria egenskaper för några av de egenskaper som du har lagt till i din entitet, oavsett om du gjorde egenskapen valfri i modellredigeraren.

även om detta kanske låter konstigt först, är det faktiskt inte så konstigt. Ta en titt på följande NSManagedObject underklass:

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

en av de två egenskaperna för min ToDoItem är valfri även om de båda krävs i modellredigeraren. När jag skapar en instans av denna ToDoItem, skulle jag använda följande kod:

let item = ToDoItem(context: managedObjectContext)

ett hanterat objekts initialiserare tar ett hanterat objektkontext. Det betyder att jag inte tilldelar ett värde till de hanterade egenskaperna under initialiseringen av ToDoItem. Skriva ut värdet för både egenskaperna label och completed ger och intressant resultat:

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

medan label är nil som förväntat, tilldelade kärndata ett standardvärde på false till egenskapen completed vilket är vettigt eftersom Xcode genererade en icke-valfri egenskap för completed. Låt oss ta det ett steg längre och ta en titt på följande kod:

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

när du kör den här koden kommer du att upptäcka att den producerar följande utdata:

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

det här felet säger tydligt completed is a required value. vilket innebär att completed inte är inställt, och det utskrivna hanterade objektet som visas tillsammans med felmeddelandet visar också att completedär nil. Så medan det finns någon form av ett standardvärde närvarande för completed, anses det inte vara icke-nil tills det uttryckligen tilldelas.

för att förstå vad som händer kan vi tilldela ett värde till completed och titta på den tryckta beskrivningen för item igen:

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

denna kod ger följande utgång:

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

det här är ganska intressant, eller hur?

egenskapen completed definieras som en Bool, men den skrivs ut som ett tal. Vi kan hitta orsaken till detta i den underliggande SQLite-butiken. Det enklaste sättet att utforska din Core data stores SQLite-fil är att skicka -com.apple.CoreData.SQLDebug 1 som ett lanseringsargument till din app och öppna SQLite-filen som Core Data ansluter till i en SQLite explorer som SQLite database browser.

tips:
Läs mer om Kärndatalanseringsargument i det här inlägget.

när du tittar på schemadefinitionen för ZTODOITEM hittar du att den använder INTEGER som typ för ZCOMPLETED. Detta innebär att egenskapen completed lagras som ett heltal i den underliggande SQLite-butiken. Anledningen till att completed lagras som en INTEGER är enkel. SQLite har inte en BOOLEAN – typ och använder ett INTEGER – värde på 0 för att representera false och 1 för att representera true istället.

data som du ser utskriven när du skriver ut din hanterade objektinstans är inte värdet för egenskapen completed, det är värdet för completed som kommer att skrivas till SQLite-butiken.

det finns två saker att lära av det här avsnittet.

först vet du nu att det finns en felaktig matchning mellan alternativen för din definierade Kärndatamodell och de genererade hanterade objekten. En icke-valfri String representeras som en valfri String i din genererade modell medan en icke-valfri Bool representeras som en icke-valfri Bool i din genererade modell.

för det andra lärde du dig att det finns en skillnad mellan hur ett värde representeras i din hanterade objektmodell jämfört med hur det representeras i den underliggande SQLite-butiken. För att se vilka värden som används för att skriva din hanterade objektinstans till den underliggande lagringen kan du skriva ut det hanterade objektet och läsa fältet data i utskriften.

huvudlektionen här är att din Kärndatamodell i modellredigeraren och dina hanterade objektunderklasser inte representerar data på samma sätt. Valfritt i din Kärndatamodell betyder inte alltid valfritt i din hanterade objektunderklass och vice versa. Ett icke-valfritt värde i din Kärndatamodell kan representeras som ett valfritt värde i underklassen hanterade objekt. Core Data validerar ditt hanterade objekt mot dess hanterade objektmodell när du försöker skriva det till den ihållande butiken och kasta fel om det stöter på några valideringsfel.

så varför finns denna obalans? Skulle det inte vara mycket lättare om den hanterade objektmodellen och hanterade objektunderklasser hade en direkt kartläggning?

förstå skillnaden mellan hanterade objekt och Kärndatamodellen

en stor del av anledningen till att det finns en obalans mellan dina hanterade objekt och den modell du har definierat i modellredigeraren kommer från Core datas Objective-C-rötter.

eftersom Objective-C inte hanterar Optional alls finns det inte alltid en bra kartläggning från modelldefinitionen till Swift-koden. Ofta, hur kartläggningen fungerar verkar något godtyckligt. Till exempel kan Optional<String> och Optional<Bool> båda inte representeras som en typ i Objective-C av den enkla anledningen att Optional inte existerar i Objective-C. Swift och Objective-C kan emellertid samverka med varandra och Optional<String> kan överbryggas till en NSString automatiskt. Tyvärr kan Optional<Bool> inte mappas till någonting i Objective-C automatiskt eftersom Xcode kommer att berätta när du försöker definiera en @NSManaged egenskap som Bool?.

om du aldrig har arbetat med Objective-C kan det tyckas väldigt konstigt för dig att det inte finns något begrepp om Optional. Hur använde folk valfria egenskaper i kärndata före Swift? Och vad händer när något ska vara nil i Objective-C?

i Objective-C är det helt bra för något värde att vara nil, även när du inte förväntar dig det. Och eftersom kärndata har sina rötter i Objective-C överför en del av detta arv till dina genererade Swift-klasser på ett ibland mindre än idealiskt sätt.

den viktigaste takeaway här är inte hur Objective-C fungerar, eller hur Xcode genererar kod exakt. Istället vill jag att du kommer ihåg att typerna och konfigurationen i din Core Data model definition inte (måste) matcha typerna i din (genererade) hanterade objektunderklass.

Sammanfattningsvis

i den här veckans artikel har du lärt dig mycket om hur dina hanterade objektunderklasser och Core Data model definition inte alltid stämmer upp som du förväntar dig. Du såg att ibland kan en icke-valfri egenskap i modellredigeraren hamna som valfri i den genererade hanterade objektunderklassen, och andra gånger hamnar den som en icke-valfri egenskap med ett standardvärde även om du inte tilldelade ett standardvärde själv.

du såg också att om ett standardvärde finns i en hanterad objektinstans betyder det inte att värdet faktiskt finns när du sparar ditt hanterade objekt om du inte uttryckligen definierade ett standardvärde i Core Data model editor.

även om detta verkligen är förvirrande och olyckligt, är kärndata ganska bra för att berätta vad som är fel i de fel som det kastar medan du sparar ett hanterat objekt. Det är också möjligt att inspektera de värden som Core Data kommer att försöka lagra genom att skriva ut din hanterade objektinstans och inspektera attributet data.

på en personlig anteckning hoppas jag att beteendet jag beskrev i veckans artikel behandlas i en framtida uppdatering till Core Data som gör det snabbare vänligt där de hanterade objektunderklasserna har en närmare, eventuellt direkt mappning till Core Data model som definieras i en modellredigerare. Men fram till dess är det viktigt att förstå att modellredigeraren och dina hanterade objektunderklasser inte representerar din modell på samma sätt, och att detta åtminstone delvis är relaterat till Core datas Objective-C-rötter.

om du har några frågor, korrigeringar eller feedback om det här inlägget, vänligen meddela mig på Twitter. Det här inlägget är en del av en del av den forskning, utforskning och förberedelse som jag gör för en bok om kärndata som jag jobbar med. För uppdateringar om den här boken se till att följa mig på Twitter. Jag planerar för närvarande att släppa boken i slutet av 2020.

Håll dig uppdaterad med mitt veckobrev

praktisk kombinera

lär dig allt du behöver veta om kombinera och hur du kan använda den i dina projekt med min nya bok praktisk kombinera. Du får tretton kapitel, en lekplats och en handfull provprojekt som hjälper dig att komma igång med Combine så snart som möjligt.

boken är tillgänglig som en digital nedladdning för bara $29.99!

Få Praktisk Kombinera

Lämna ett svar

Din e-postadress kommer inte publiceras.