Verstehen der Unterschiede zwischen Ihrem Kerndatenmodell und verwalteten Objekten

Veröffentlicht von donnywals am Oktober 5, 2020

Möglicherweise haben Sie bemerkt, dass die meisten Eigenschaften Ihres verwalteten Objekts optional sind, wenn Xcode Ihre NSManagedObject -Klassen basierend auf Ihrer Core Data Model-Datei generiert. Selbst wenn Sie sie im Modelleditor erforderlich gemacht haben, generiert Xcode ein verwaltetes Objekt, bei dem die meisten Eigenschaften optional sind.

In diesem Artikel werden wir dieses Phänomen untersuchen und warum es passiert.

Automatisch generierte NSManagedObject-Unterklassen

Wenn Sie ein Projekt erstellen, das die automatische Codegenerierung von Xcode für Kerndatenmodelle verwendet, werden Ihre NSManagedObject -Unterklassen beim Erstellen Ihres Projekts generiert. Diese Klassen befinden sich im abgeleiteten Datenordner Ihres Projekts und sollten nicht direkt geändert werden. Die von Xcode generierten Modelle verfügen über optionale Eigenschaften für einige der Eigenschaften, die Sie Ihrer Entität hinzugefügt haben, unabhängig davon, ob Sie die Eigenschaft im Modelleditor optional gemacht haben.

Das mag zunächst seltsam klingen, ist aber eigentlich nicht so seltsam. Schauen Sie sich die folgende NSManagedObject -Unterklasse an:

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

Eine der beiden Eigenschaften für my ToDoItem ist optional, auch wenn beide im Modelleditor erforderlich sind. Wenn ich eine Instanz dieses ToDoItem , würde ich den folgenden Code verwenden:

let item = ToDoItem(context: managedObjectContext)

Der Initialisierer eines verwalteten Objekts verwendet einen verwalteten Objektkontext. Dies bedeutet, dass ich den verwalteten Eigenschaften während der Initialisierung von ToDoItem keinen Wert zuweise. Das Drucken des Werts für die Eigenschaften label und completed ergibt ein interessantes Ergebnis:

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

Während label wie erwartet nil ist, hat Core Data der completed -Eigenschaft einen Standardwert von false zugewiesen, was sinnvoll ist, da Xcode eine nicht optionale Eigenschaft für completed generiert hat. Gehen wir noch einen Schritt weiter und schauen uns den folgenden Code an:

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

Wenn Sie diesen Code ausführen, werden Sie feststellen, dass er die folgende Ausgabe erzeugt:

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

Dieser Fehler besagt eindeutig completed is a required value., was bedeutet, dass completed nicht festgelegt ist, und das gedruckte verwaltete Objekt, das neben der Fehlermeldung angezeigt wird, zeigt auch an, dass completed nil ist. Während also für completed ein Standardwert vorhanden ist, wird er erst dann als Nicht-nil betrachtet, wenn er explizit zugewiesen wird.

Um zu verstehen, was passiert, können wir completed einen Wert zuweisen und uns die gedruckte Beschreibung für item erneut ansehen:

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

Dieser Code erzeugt die folgende Ausgabe:

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

Das ist ziemlich interessant, nicht wahr?

Die Eigenschaft completed ist als Bool definiert, wird jedoch als Zahl gedruckt. Wir können den Grund dafür im zugrunde liegenden SQLite-Speicher finden. Der einfachste Weg, die SQLite-Datei Ihres Core Data Store zu erkunden, besteht darin, -com.apple.CoreData.SQLDebug 1 als Startargument an Ihre App zu übergeben und die SQLite-Datei, mit der Core Data eine Verbindung herstellt, in einem SQLite-Explorer wie dem SQLite-Datenbankbrowser zu öffnen.

Tipp:
In diesem Beitrag erfahren Sie mehr über die Argumente zum Starten von Core Data.

Wenn Sie sich die Schemadefinition für ZTODOITEM ansehen, werden Sie feststellen, dass INTEGER als Typ für ZCOMPLETED verwendet wird. Dies bedeutet, dass die Eigenschaft completed als Ganzzahl im zugrunde liegenden SQLite-Speicher gespeichert wird. Der Grund, warum completed als INTEGER gespeichert wird, ist einfach. SQLite hat keinen BOOLEAN -Typ und verwendet stattdessen einen INTEGER -Wert von 0 für false und 1 für true .

Das data , das beim Drucken Ihrer verwalteten Objektinstanz gedruckt wird, ist nicht der Wert für Ihre completed -Eigenschaft, sondern der Wert für completed, der in den SQLite-Speicher geschrieben wird.

In diesem Abschnitt gibt es zwei Dinge zu lernen.

Zunächst wissen Sie jetzt, dass es eine Diskrepanz zwischen der Optionalität Ihres definierten Kerndatenmodells und den generierten verwalteten Objekten gibt. Ein nicht optionales String wird in Ihrem generierten Modell als optionales String dargestellt, während ein nicht optionales Bool in Ihrem generierten Modell als nicht optionales Bool dargestellt wird.

Zweitens haben Sie gelernt, dass es einen Unterschied zwischen der Darstellung eines Werts in Ihrem verwalteten Objektmodell und der Darstellung im zugrunde liegenden SQLite-Speicher gibt. Um zu sehen, welche Werte zum Schreiben Ihrer verwalteten Objektinstanz in den zugrunde liegenden Speicher verwendet werden, können Sie das verwaltete Objekt drucken und das Feld data in der gedruckten Ausgabe lesen.

Die wichtigste Lektion hier ist, dass Ihr Kerndatenmodell im Modelleditor und Ihre verwalteten Objektunterklassen Daten nicht auf dieselbe Weise darstellen. Optional in Ihrem Kerndatenmodell bedeutet nicht immer optional in Ihrer Unterklasse für verwaltete Objekte und umgekehrt. Ein nicht optionaler Wert in Ihrem Kerndatenmodell kann als optionaler Wert in Ihrer Unterklasse für verwaltete Objekte dargestellt werden. Core Data überprüft Ihr verwaltetes Objekt anhand seines verwalteten Objektmodells, wenn Sie versuchen, es in den persistenten Speicher zu schreiben, und gibt Fehler aus, wenn Validierungsfehler auftreten.

Warum gibt es diese Nichtübereinstimmung? Wäre es nicht viel einfacher, wenn das verwaltete Objektmodell und die verwalteten Objektunterklassen eine direkte Zuordnung hätten?

Verständnis der Diskrepanz zwischen verwalteten Objekten und dem Core Data-Modell

Ein großer Teil des Grundes, warum es eine Diskrepanz zwischen Ihren verwalteten Objekten und dem Modell gibt, das Sie im Modelleditor definiert haben, kommt von den Objective-C-Wurzeln von Core Data.

Da Objective-C überhaupt nicht mit Optional umgeht, gibt es nicht immer eine gute Zuordnung von der Modelldefinition zum Swift-Code. Oft scheint die Art und Weise, wie das Mapping funktioniert, etwas willkürlich zu sein. Zum Beispiel können Optional<String> und Optional<Bool> beide nicht als Typ in Objective-C dargestellt werden, aus dem einfachen Grund, dass Optional in Objective-C nicht existiert. Swift und Objective-C können jedoch miteinander interagieren und Optional<String> kann automatisch zu einem NSString überbrückt werden. Leider kann Optional<Bool> in Objective-C nicht automatisch zugeordnet werden, da Xcode Ihnen dies mitteilt, wenn Sie versuchen, eine @NSManaged -Eigenschaft als Bool? zu definieren.

Wenn Sie noch nie mit Objective-C gearbeitet haben, mag es Ihnen sehr seltsam erscheinen, dass es kein Konzept von Optional gibt. Wie haben die Leute optionale Eigenschaften in Core Data vor Swift verwendet? Und was passiert, wenn in Objective-C etwas nil sein soll?

In Objective-C ist es vollkommen in Ordnung, wenn ein Wert nil ist, auch wenn Sie es nicht erwarten. Und da Core Data seine Wurzeln in Objective-C hat, überträgt sich ein Teil dieses Vermächtnisses auf Ihre generierten Swift-Klassen auf manchmal weniger als ideale Weise.

Das wichtigste hier ist nicht, wie Objective-C funktioniert oder wie Xcode Code genau generiert. Stattdessen möchten wir Sie daran erinnern, dass die Typen und die Konfiguration in Ihrer Kerndatenmodelldefinition nicht mit den Typen in Ihrer (generierten) Unterklasse für verwaltete Objekte übereinstimmen (müssen).

Zusammenfassend

Im Artikel dieser Woche haben Sie viel darüber gelernt, wie Ihre verwalteten Objektunterklassen und die Definition des Kerndatenmodells nicht immer so ausgerichtet sind, wie Sie es erwarten würden. Sie haben festgestellt, dass eine nicht optionale Eigenschaft im Modelleditor manchmal in der generierten Unterklasse für verwaltete Objekte als optional und manchmal als nicht optionale Eigenschaft mit einem Standardwert angezeigt wird, selbst wenn Sie keinen Standardwert zugewiesen haben selbst.

Sie haben auch festgestellt, dass ein Standardwert für eine verwaltete Objektinstanz nicht bedeutet, dass der Wert zum Zeitpunkt des Speicherns Ihres verwalteten Objekts tatsächlich vorhanden ist, es sei denn, Sie haben im Core Data Model Editor explizit einen Standardwert definiert.

Obwohl dies sicherlich verwirrend und unglücklich ist, kann Core Data Ihnen ziemlich gut sagen, was an den Fehlern falsch ist, die beim Speichern eines verwalteten Objekts ausgelöst werden. Es ist auch möglich, die Werte zu überprüfen, die Core Data zu speichern versucht, indem Sie Ihre verwaltete Objektinstanz drucken und das Attribut data überprüfen.

Persönlich hoffe ich, dass das Verhalten, das ich im Artikel dieser Woche beschrieben habe, in einem zukünftigen Update von Core Data behoben wird, das es benutzerfreundlicher macht, wenn die verwalteten Objektunterklassen eine engere, möglicherweise direkte Zuordnung zum Core Data-Modell haben, das in einem Modelleditor definiert ist. Bis dahin ist es jedoch wichtig zu verstehen, dass der Modelleditor und Ihre verwalteten Objektunterklassen Ihr Modell nicht auf dieselbe Weise darstellen und dass dies zumindest teilweise mit den Objective-C-Wurzeln von Core Data zusammenhängt.

Wenn Sie Fragen, Korrekturen oder Feedback zu diesem Beitrag haben, lassen Sie es mich bitte auf Twitter wissen. Dieser Beitrag ist Teil einiger Recherchen, Erkundungen und Vorbereitungen, die ich für ein Buch über Core Data mache, an dem ich arbeite. Für Updates zu diesem Buch folgen Sie mir auf Twitter. Ich plane derzeit, das Buch gegen Ende 2020 zu veröffentlichen.

Bleiben Sie auf dem Laufenden mit meinem wöchentlichen Newsletter

Practical Combine

Erfahren Sie in meinem neuen Buch Practical Combine alles, was Sie über Combine wissen müssen und wie Sie es in Ihren Projekten einsetzen können. Sie erhalten dreizehn Kapitel, einen Spielplatz und eine Handvoll Beispielprojekte, damit Sie so schnell wie möglich mit Combine loslegen können.

Das Buch ist als digitaler Download für nur $ 29.99 erhältlich!

Holen Sie sich Praktische Kombinieren

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.