Comprendere le differenze tra il modello di dati di base e gli oggetti gestiti

Pubblicato da donnywals ottobre 5, 2020

Potresti aver notato che quando Xcode genera le classi NSManagedObject in base al file del modello di dati di base, la maggior parte delle proprietà dell’oggetto gestito sono facoltative. Anche se li hai resi necessari nell’editor del modello, Xcode genererà un oggetto gestito in cui la maggior parte delle proprietà sono facoltative.

In questo articolo esploreremo questo fenomeno e perché succede.

Esplorazione delle sottoclassi NSManagedObject generate

Quando si crea un progetto che utilizza la generazione automatica di codice Xcode per i modelli di dati principali, le sottoclassi NSManagedObject vengono generate quando si crea il progetto. Queste classi sono scritte nella cartella dei dati derivati del tuo progetto e non dovresti modificarle direttamente. I modelli generati da Xcode avranno proprietà facoltative per alcune delle proprietà aggiunte all’entità, indipendentemente dal fatto che la proprietà sia stata resa facoltativa nell’editor del modello.

Anche se questo potrebbe sembrare strano all’inizio, in realtà non è così strano. Dai un’occhiata alla seguente sottoclasse 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 delle due proprietà per il mio ToDoItem è facoltativa anche se sono entrambe richieste nell’editor del modello. Quando creo un’istanza di questo ToDoItem, userei il seguente codice:

let item = ToDoItem(context: managedObjectContext)

L’inizializzatore di un oggetto gestito accetta un contesto oggetto gestito. Ciò significa che non assegno un valore alle proprietà gestite durante l’inizializzazione di ToDoItem. La stampa del valore per entrambe le proprietà label e completed produce risultati interessanti:

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

Mentre label è nil come previsto, Core Data ha assegnato un valore predefinito di false alla proprietà completed che ha senso perché Xcode ha generato una proprietà non opzionale per completed. Facciamo un ulteriore passo avanti e diamo un’occhiata al seguente codice:

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

Quando esegui questo codice, scoprirai che produce il seguente output:

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

Questo errore dice chiaramente completed is a required value. che implica che completed non è impostato e l’oggetto gestito stampato visualizzato accanto al messaggio di errore mostra anche che completedè nil. Quindi, mentre c’è una sorta di valore predefinito presente per completed, non è considerato non-nil fino a quando non viene assegnato esplicitamente.

Per capire cosa sta succedendo, possiamo assegnare un valore a completed e dare un’occhiata alla descrizione stampata per item di nuovo:

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

Questo codice produce il seguente output:

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

E ‘ molto interessante, vero?

La proprietà completed è definita come Bool, ma viene stampata come un numero. Possiamo trovare la ragione di questo nel negozio SQLite sottostante. Il modo più semplice per esplorare il file SQLite del tuo Core Data store è passare -com.apple.CoreData.SQLDebug 1 come argomento di avvio alla tua app e aprire il file SQLite a cui Core Data si connette in un esploratore SQLite come SQLite database browser.

Suggerimento:
Scopri di più sugli argomenti di avvio dei dati principali in questo post.

Quando guardi la definizione dello schema per ZTODOITEM scoprirai che usa INTEGERcome tipo per ZCOMPLETED. Ciò significa che la proprietà completed viene memorizzata come un numero intero nell’archivio SQLite sottostante. Il motivo per cui completed è memorizzato come INTEGER è semplice. SQLite non ha un tipo BOOLEAN e utilizza un valore INTEGER di 0 per rappresentare false e 1 per rappresentare true.

Il data che viene visualizzato stampato quando si stampa l’istanza dell’oggetto gestito non è il valore per la proprietà completed, è il valore per completed che verrà scritto nell’archivio SQLite.

Ci sono due cose da imparare da questa sezione.

Innanzitutto, ora sai che esiste una discrepanza tra l’opzionalità del tuo modello di dati core definito e gli oggetti gestiti generati. Un String non opzionale è rappresentato come String opzionale nel modello generato mentre un Bool non opzionale è rappresentato come Bool non opzionale nel modello generato.

In secondo luogo, è stato appreso che esiste una differenza tra il modo in cui un valore viene rappresentato nel modello a oggetti gestito e il modo in cui viene rappresentato nell’archivio SQLite sottostante. Per vedere quali valori vengono utilizzati per scrivere l’istanza dell’oggetto gestito nella memoria sottostante, è possibile stampare l’oggetto gestito e leggere il campo data nell’output stampato.

La lezione principale qui è che il tuo modello di dati core nell’editor del modello e le sottoclassi degli oggetti gestiti non rappresentano i dati allo stesso modo. Facoltativo nel modello di dati di base non significa sempre facoltativo nella sottoclasse degli oggetti gestiti e viceversa. Un valore non facoltativo nel modello di dati di base può essere rappresentato come valore facoltativo nella sottoclasse degli oggetti gestiti. Core Data convaliderà l’oggetto gestito rispetto al suo modello di oggetto gestito quando si tenta di scriverlo nell’archivio persistente e generare errori se rileva errori di convalida.

Quindi perché esiste questa mancata corrispondenza? Non sarebbe molto più semplice se il modello a oggetti gestito e le sottoclassi di oggetti gestiti avessero una mappatura diretta?

Comprendere la mancata corrispondenza tra gli oggetti gestiti e il modello di dati di base

Una grande parte del motivo per cui c’è una mancata corrispondenza tra gli oggetti gestiti e il modello che hai definito nell’editor del modello proviene dalle radici Objective-C di Core Data.

Poiché Objective-C non si occupa affatto di Optional non c’è sempre una buona mappatura dalla definizione del modello al codice Swift. Spesso, il modo in cui funziona la mappatura sembra un po ‘ arbitrario. Ad esempio, Optional<String> e Optional<Bool> entrambi non possono essere rappresentati come un tipo in Objective-C per il semplice motivo che Optional non esiste in Objective-C. Tuttavia, Swift e Objective-C possono interagire tra loro e Optional<String> può essere collegato automaticamente a un NSString. Sfortunatamente Optional<Bool> non può essere mappato a nulla in Objective-C automaticamente poiché Xcode ti dirà quando tenti di definire una proprietà @NSManagedcome Bool?.

Se non hai mai lavorato con Objective-C potrebbe sembrarti molto strano che non ci sia il concetto di Optional. In che modo la gente ha usato le proprietà opzionali nei dati principali prima di Swift? E cosa succede quando qualcosa dovrebbe essere nil in Objective-C?

In Objective-C va perfettamente bene che qualsiasi valore sia nil, anche quando non te lo aspetti. E poiché Core Data ha le sue radici in Objective-C, parte di questa eredità viene trasferita alle classi Swift generate in un modo a volte meno che ideale.

L’asporto più importante qui non è come funziona Objective-C, o come Xcode genera esattamente il codice. Invece, voglio che tu ricordi che i tipi e la configurazione nella definizione del modello di dati di base non corrispondono (devono) ai tipi nella sottoclasse dell’oggetto gestito (generato).

In sintesi

Nell’articolo di questa settimana hai imparato molto su come le sottoclassi degli oggetti gestiti e la definizione del modello di dati di base non sempre si allineano nel modo in cui ti aspetteresti. Hai visto che a volte una proprietà non facoltativa nell’editor del modello può finire come facoltativa nella sottoclasse dell’oggetto gestito generato e altre volte finisce come una proprietà non facoltativa con un valore predefinito anche se non hai assegnato tu stesso un valore predefinito.

Hai anche visto che se un valore predefinito è presente su un’istanza di oggetto gestito, ciò non significa che il valore sia effettivamente presente al momento del salvataggio dell’oggetto gestito a meno che tu non abbia definito esplicitamente un valore predefinito nell’editor del modello di dati di base.

Mentre questo è certamente confuso e sfortunato, Core Data è abbastanza bravo a dirti cosa c’è di sbagliato negli errori che genera durante il salvataggio di un oggetto gestito. È anche possibile ispezionare i valori che Core Data tenterà di archiviare stampando l’istanza dell’oggetto gestito e ispezionando il suo attributo data.

In una nota personale spero che il comportamento che ho descritto nell’articolo di questa settimana sia affrontato in un futuro aggiornamento ai dati di base che lo rende più rapido e amichevole in cui le sottoclassi degli oggetti gestiti hanno una mappatura più vicina, possibilmente diretta al modello di dati di base definito in un editor di modelli. Ma fino ad allora, è importante capire che l’editor del modello e le sottoclassi degli oggetti gestiti non rappresentano il modello allo stesso modo e che questo è almeno parzialmente correlato alle radici Objective-C di Core Data.

Se avete domande, correzioni o commenti su questo post fatemelo sapere su Twitter. Questo post fa parte di alcune delle ricerche, l’esplorazione e la preparazione che sto facendo per un libro sui dati di base che sto lavorando. Per gli aggiornamenti su questo libro assicuratevi di seguirmi su Twitter. Attualmente sto progettando di rilasciare il libro intorno alla fine del 2020.

Rimani aggiornato con la mia newsletter settimanale

Pratico combinare

Scopri tutto quello che c’è da sapere su Combinare e come si può utilizzare nei vostri progetti con il mio nuovo libro Pratico Combinare. Otterrete tredici capitoli, un parco giochi e una manciata di progetti di esempio per aiutarvi a ottenere installato e funzionante con Combinare il più presto possibile.

Il libro è disponibile come download digitale per soli $29.99!

Ottieni una combinazione pratica

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.