Comprender las diferencias entre el modelo de datos central y los objetos administrados

Publicado por donnywals en octubre 5, 2020

Es posible que haya notado que cuando Xcode genera sus clases NSManagedObject basadas en su archivo de modelo de datos central, la mayoría de las propiedades de su objeto administrado son opcionales. Incluso si los ha hecho necesarios en el editor de modelos, Xcode generará un objeto administrado donde la mayoría de las propiedades son opcionales.

En este artículo exploraremos este fenómeno y por qué sucede.

Explorar subclases generadas de NSManagedObject

Cuando compila un proyecto que utiliza la generación automática de código de Xcode para modelos de datos básicos, las subclases NSManagedObject se generan al compilar el proyecto. Estas clases se escriben en la carpeta de Datos derivados de tu proyecto y no debes modificarlas directamente. Los modelos generados por Xcode tendrán propiedades opcionales para algunas de las propiedades que haya agregado a su entidad, independientemente de si hizo la propiedad opcional en el editor de modelos.

Si bien esto puede sonar extraño al principio, en realidad no es tan extraño. Echa un vistazo a la siguiente subclase 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 de las dos propiedades de my ToDoItem es opcional, aunque ambas sean necesarias en el editor de modelos. Al crear una instancia de este ToDoItem, usaría el siguiente código:

let item = ToDoItem(context: managedObjectContext)

El inicializador de un objeto administrado toma un contexto de objeto administrado. Esto significa que no asigno un valor a las propiedades administradas durante la inicialización de ToDoItem. Imprimir el valor de las propiedades label y completed produce un resultado interesante:

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

Mientras que label es nil como se esperaba, Core Data asignó un valor predeterminado de false a la propiedad completed, lo que tiene sentido porque Xcode generó una propiedad no opcional para completed. Vamos un paso más allá y echemos un vistazo al siguiente código:

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

Cuando ejecuta este código, encontrará que produce la siguiente salida:

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

Este error dice claramente completed is a required value., lo que implica que completed no está establecido, y el objeto administrado impreso que se muestra junto al mensaje de error también muestra que completed es nil. Por lo tanto, si bien hay algún tipo de valor predeterminado presente para completed, no se considera que no seanil hasta que se asigna explícitamente.

Para entender lo que está sucediendo, podemos asignar un valor a completed y echar un vistazo a la descripción impresa de item de nuevo:

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

Este código produce la siguiente salida:

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

Esto es muy interesante, ¿no?

La propiedad completed se define como Bool, pero se imprime como un número. Podemos encontrar la razón de esto en la tienda SQLite subyacente. La forma más fácil de explorar el archivo SQLite de su almacén de datos principales es pasar -com.apple.CoreData.SQLDebug 1 como argumento de inicio a su aplicación y abrir el archivo SQLite al que se conecta Core Data en un explorador SQLite como el explorador de bases de datos SQLite.

Sugerencia:
Obtenga más información sobre los argumentos de lanzamiento de datos principales en esta publicación.

Si observa la definición de esquema de ZTODOITEM, encontrará que utiliza INTEGER como tipo de ZCOMPLETED. Esto significa que la propiedad completed se almacena como un entero en el almacén SQLite subyacente. La razón por la que completed se almacena como INTEGER es simple. SQLite no tiene un tipo BOOLEAN y utiliza un valor INTEGER de 0 para representar false, y 1 para representar true en su lugar.

El data que ve impreso cuando imprime su instancia de objeto administrado no es el valor de su propiedad completed, es el valor de completed que se escribirá en la tienda SQLite.

Hay dos cosas que aprender de esta sección.

En primer lugar, ahora sabe que hay un desajuste entre la opcionalidad de su modelo de datos básicos definido y los objetos administrados generados. Un String no opcional se representa como un String opcional en el modelo generado, mientras que un Bool no opcional se representa como un Bool no opcional en el modelo generado.

En segundo lugar, aprendió que hay una diferencia entre cómo se representa un valor en su modelo de objetos administrados y cómo se representa en la tienda SQLite subyacente. Para ver qué valores se utilizan para escribir la instancia de objeto administrado en el almacenamiento subyacente, puede imprimir el objeto administrado y leer el campo data en la salida impresa.

La lección principal aquí es que el modelo de datos Central en el editor de modelos y las subclases de objetos administrados no representan los datos de la misma manera. Opcional en el modelo de datos principales no siempre significa opcional en la subclase de objetos administrados y viceversa. Un valor no opcional en el modelo de datos principales puede representarse como un valor opcional en la subclase de objetos administrados. Los datos principales validarán el objeto administrado contra su modelo de objeto administrado cuando intente escribirlo en el almacén persistente y generar errores si encuentra errores de validación.

Entonces, ¿por qué existe este desajuste? ¿No sería mucho más fácil si el modelo de objetos administrados y las subclases de objetos administrados tuvieran una asignación directa?

Comprender el desajuste entre los objetos administrados y el modelo de datos Central

Una gran parte de la razón por la que hay un desajuste entre los objetos administrados y el modelo que ha definido en el editor de modelos proviene de las raíces de Objective-C de Core Data.

Dado que Objective-C no se ocupa de Optional en absoluto, no siempre hay una buena asignación de la definición del modelo al código Swift. A menudo, la forma en que funciona el mapeo parece algo arbitrario. Por ejemplo, Optional<String> y Optional<Bool> tanto no puede ser representado como un tipo en Objective-C, por la sencilla razón de que Optional no existe en Objective-C. sin Embargo, Swift y Objective-C puede interoperabilidad con otros y Optional<String> puede direccionarse a un NSString automáticamente. Desafortunadamente, Optional<Bool> no se puede asignar a nada en Objective-C automáticamente, ya que Xcode le indicará cuando intente definir una propiedad @NSManaged como Bool?.

Si nunca ha trabajado con Objective-C, le puede parecer muy extraño que no exista el concepto de Optional. ¿Cómo usaban las propiedades opcionales en los datos principales antes de Swift? ¿Y qué sucede cuando se supone que algo es nil en Objective-C?

En Objective-C está perfectamente bien que cualquier valor sea nil, incluso cuando no lo esperas. Y dado que los datos principales tienen sus raíces en Objective-C, parte de este legado se transfiere a las clases Swift generadas de una manera a veces menos que ideal.

Lo más importante aquí no es cómo funciona Objective-C, o cómo Xcode genera código exactamente. En su lugar, quiero que recuerde que los tipos y la configuración de la definición del modelo de datos central no (tienen que) coincidir con los tipos de la subclase de objetos administrados (generada).

En resumen

En el artículo de esta semana, ha aprendido mucho sobre cómo las subclases de objetos administrados y la definición del modelo de datos centrales no siempre se alinean de la manera que esperaría. Vio que a veces una propiedad no opcional en el editor de modelos puede terminar como opcional en la subclase de objetos administrados generados, y otras veces termina como una propiedad no opcional con un valor predeterminado, incluso si no asignó un valor predeterminado usted mismo.

También vio que si un valor predeterminado está presente en una instancia de objeto administrado, no significa que el valor esté realmente presente en el momento de guardar el objeto administrado, a menos que haya definido explícitamente un valor predeterminado en el editor de modelos de datos principales.

Si bien esto es ciertamente confuso y desafortunado, Core Data es bastante bueno para decirle lo que está mal en los errores que arroja al guardar un objeto administrado. También es posible inspeccionar los valores que Core Data intentará almacenar imprimiendo su instancia de objeto administrado e inspeccionando su atributo data.

En una nota personal, espero que el comportamiento que describí en el artículo de esta semana se aborde en una actualización futura de Core Data que lo haga más amigable con Swift, donde las subclases de objetos administrados tengan una asignación más cercana, posiblemente directa, al modelo de datos Core definido en un editor de modelos. Pero hasta entonces, es importante entender que el editor de modelos y sus subclases de objetos administrados no representan su modelo de la misma manera, y que esto está al menos parcialmente relacionado con las raíces de Objective-C de Core Data.

Si tienes alguna pregunta, corrección o comentario sobre esta publicación, házmelo saber en Twitter. Este post es parte de la investigación, exploración y preparación que estoy haciendo para un libro sobre Datos Centrales en el que estoy trabajando. Para actualizaciones sobre este libro, asegúrate de seguirme en Twitter. Actualmente estoy planeando lanzar el libro a finales de 2020.

Manténgase al día con mi boletín semanal

Combine práctico

Aprenda todo lo que necesita saber sobre Combine y cómo puede usarlo en sus proyectos con mi nuevo libro Combine práctico. Obtendrás trece capítulos, un patio de juegos y un puñado de proyectos de muestra para ayudarte a ponerte en marcha con Combine lo antes posible.

¡El libro está disponible como descarga digital por solo $29.99!

Obtenga una combinación práctica

Deja una respuesta

Tu dirección de correo electrónico no será publicada.