Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. Mobile Development
Code

Core data desde cero: Relaciones y más obtención

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Core Data from Scratch.
Core Data from Scratch: Managed Objects and Fetch Requests
Core Data from Scratch: NSFetchedResultsController

Spanish (Español) translation by CYC (you can also view the original English article)

En el artículo anterior, aprendimos sobre NSManagedObject y lo fácil que es crear, leer, actualizar y eliminar registros utilizando Core Data. Sin embargo, no mencioné las relaciones en esa discusión. Además de algunas advertencias que debes tener en cuenta, las relaciones son tan fáciles de manipular como los atributos. En este artículo, nos centraremos en las relaciones y también continuaremos nuestra exploración de NSFetchRequest.

1. Relaciones

Ya hemos trabajado con relaciones en el editor de modelos de Core Data y, por lo tanto, lo que voy a contarles les resultará familiar. Las relaciones son, al igual que los atributos, accesibles mediante la codificación de clave-valor. Recuerda que el modelo de datos que creamos anteriormente en esta serie define una entidad de Persona y una entidad de Dirección. Una persona está vinculada a una o más direcciones y una dirección está vinculada a una o más personas. Esta es una relación de muchos a muchos.

Para captar las direcciones de una persona, simplemente invocamos valueForKey: en la persona, una instancia de NSManagedObject, y pasamos addresses como la clave. Ten en cuenta que addresses es la clave que definimos en el modelo de datos. ¿Qué tipo de objeto esperas? La mayoría de las personas nuevas en Core Data esperan un NSArray ordenado, pero Core Data devuelve un NSSet, que no está ordenado. Trabajar con NSSet tiene sus ventajas, como sabrás más adelante.

Creando Registros

Ya basta con la teoría, abre el proyecto del artículo anterior o clónalo de GitHub. Comencemos por crear una persona y luego vincularla a una dirección. Para crear una persona, actualiza el método application:didFinishLaunchingWithOptions: como se muestra a continuación.

Esto debería resultar familiar si has leído el artículo anterior. La creación de una dirección es similar a la que puedes ver a continuación.

Debido a que cada atributo de la entidad Address está marcado como opcional, no es necesario asignar un valor a cada atributo. En el ejemplo anterior, solo establecimos los atributos street y city del registro.

Creando una relación

Para vincular la nueva dirección con la nueva persona, invocamos setValue:forKey:, ingresando las direcciones addresses como la clave. El valor que pasamos es un NSSet que contiene newAddress. Echa un vistazo al siguiente bloque de código para aclarar.

Llamamos save: en el contexto del objeto gestionado newPerson para propagar los cambios al almacén persistente. Recuerda que llamar a save: en un contexto de objeto gestionado guarda el estado del contexto del objeto gestionado. Esto significa que newAddress también se escribe en el respaldo almacenado, así como las relaciones que acabamos de definir.

Quizás te preguntes por qué no vinculamos newPerson con newAddress, porque definimos una relación inversa en nuestro modelo de datos. Core Data crea esta relación por nosotros. Si una relación tiene una relación inversa, Core Data se encarga de esto automáticamente. Puedes verificar esto preguntando el objeto newAddress por sus persons.

Obteniendo y actualizando una relación

Actualizar una relación tampoco es difícil. La única advertencia es que debemos agregar o eliminar elementos de las manos inmutables de Core Data de la instancia de NSSet. Sin embargo, para facilitar esta tarea, NSManagedObject declara un método práctico mutableSetValueForKey:, que devuelve un objeto NSMutableSet. Luego podemos simplemente agregar o eliminar un elemento de la colección para actualizar la relación.

Echa un vistazo al siguiente bloque de código en el que creamos otra dirección y la asociamos con newPerson. Hacemos esto invocando mutableSetValueForKey: en newPerson y agregando otherAddress al conjunto mutable. No es necesario decirle a Core Data que hemos actualizado la relación. Core Data realiza un seguimiento del conjunto mutable que nos dio y actualiza la relación en consecuencia.

Eliminar una relación

Eliminar una relación es tan simple como invocar a setValue:forKey:, pasando a nil como el valor y el nombre de la relación como la clave. Esto desvincula todas las direcciones de newPerson.

2. Relaciones uno a uno y uno a muchos

Relaciones uno a uno

Aunque nuestro modelo de datos no define una relación uno-a-uno, has aprendido todo lo que necesitas saber para trabajar con este tipo de relación. Trabajar con una relación de uno a uno es idéntico al trabajo con atributos. La única diferencia es que el valor que obtienes de valueForKey: y el valor que pasa a setValue:forKey: es una instancia de NSManagedObject.

Actualicemos nuestro modelo de datos para ilustrar esto. Abre Core_Data.xcdatamodeld y selecciona la entidad Person. Crea una nueva relación y asígnale el nombre de spouse. Establece la entidad Person como el destino y establece la relación spouse como la relación inversa.

Como puedes ver, es perfectamente posible crear una relación en la que el destino de la relación sea la misma entidad que la que define la relación. También ten en cuenta que siempre establecemos el inverso de la relación. Como dice la documentación, hay muy pocas situaciones en las que desees crear una relación que no tenga una relación inversa.

¿Sabes qué sucederá si fueras a compilar y ejecutar la aplicación? Así es, la aplicación fallaría. Debido a que cambiamos el modelo de datos, el almacenamiento existente, una base de datos SQLite en este ejemplo, ya no es compatible con el modelo de datos. Para remediar esto, elimina la aplicación de tu dispositivo o iOS Simulator y ejecuta la aplicación. Sin embargo, no te preocupes, solucionaremos este problema de manera más elegante en una futura entrega mediante migraciones.

Si puedes ejecutar la aplicación sin problemas, entonces es hora de dar el siguiente paso. Regresa a la clase delegate de la aplicación y agrega el siguiente bloque de código.

 Para configurar anotherPerson como el cónyuge de newPerson, invocamos setValue:forKey: en newPerson y pasamos en anotherPerson y @"spouse" como argumentos. Podemos lograr el mismo resultado invocando setValue:forKey: en anotherPerson y pasando newPerson y @"spouse" como argumentos.

Relaciones uno a muchos

Terminemos con un vistazo a las relaciones de uno a muchos. Abre Core_Data.xcdatamodeld, selecciona la entidad Person y crea una relación llamada children. Establece el destino en Person, establece el tipo en To Many y deja la relación inversa vacía por ahora.

Crea otra relación llamada father, establece el destino en Person y establece la relación inversa con children. Esto rellenará automáticamente la relación inversa de la relación children que dejamos en blanco hace un momento. Ahora hemos creado una relación uno a muchos, es decir, un padre puede tener muchos hijos, pero un hijo solo puede tener un padre.

Regresa al delegado de la aplicación y agrega el siguiente bloque de código. Creamos otro registro de person, establecemos sus atributos y lo configuramos como un elemento secundario de newPerson pidiendo datos principales para un conjunto mutable para los elementos clave children y agregando el nuevo registro al conjunto mutable.

El siguiente bloque de código logra el mismo resultado al establecer el atributo father de anotherChildPerson. El resultado es que newPerson se convierte en el padre de anotherChildPerson y anotherChildPerson se convierte en hija de newPerson.

3. Más obtención

El modelo de datos de nuestra aplicación de muestra ha crecido bastante en términos de complejidad. Hemos creado relaciones uno a uno, uno a muchos y muchos a muchos. Hemos visto lo fácil que es crear registros, incluidas las relaciones. Sin embargo, si también queremos poder obtener esos datos de la tienda permanente, entonces necesitamos saber más sobre la recuperación. Comencemos con un ejemplo simple en el que vemos cómo ordenar los resultados devueltos por una solicitud de búsqueda.

Ordenar descriptores

Para ordenar los registros que obtenemos del contexto del objeto gestionado, utilizamos la clase NSSortDescriptor. Echa un vistazo al siguiente fragmento de código.

Inicializamos una solicitud de extracción al pasar a la entidad que nos interesa, Person. Luego creamos un objeto NSSortDescriptor invocando sortDescriptorWithKey:ascending:, pasando el atributo de la entidad por la que nos gustaría ordenar, first, y un booleano que indica si los registros deben ordenarse en orden ascendente o descendente.

Vinculamos el descriptor de clasificación a la solicitud de recuperación invocando setSortDescriptors: en la solicitud de obtención, pasando una matriz que incluye el descriptor de clasificación. Como setSortDescriptors: acepta una matriz, es posible pasar más de un descriptor de clasificación. Echaremos un vistazo a esta opción en un momento.

El resto del bloque de código debería ser familiar. La solicitud de búsqueda se pasa al contexto del objeto gestionado, que ejecuta la solicitud de búsqueda cuando invocamos executeFetchRequest:error:. Es importante pasar siempre un puntero a un objeto NSError para saber qué falló si falla la ejecución de la solicitud de recuperación....

Ejecuta la aplicación e inspecciona la salida en la consola de Xcode. La salida debe ser similar a lo que se muestra a continuación. Como puedes ver, los registros están ordenados por su primer nombre.

Si ves duplicados en la salida, asegúrate de comentar el código que escribimos antes para crear los registros. Cada vez que ejecutas la aplicación, se crean los mismos registros, lo que genera registros duplicados.

Como mencioné anteriormente, es posible combinar múltiples descripciones de clasificación. Vamos a ordenar los registros por su apellido y edad. Primero configuramos la clave del primer descriptor de ordenación para last. Luego creamos otro descriptor de ordenación con una clave de edad que será age y lo agregamos a la matriz de descriptores de clasificación que pasamos a setSortDescriptors:

El resultado muestra que el orden de los descriptores de clasificación en la matriz es importante. El resultado muestra que el orden de los descriptores de clasificación en la matriz es importante.

Predicados

Los descriptores de clasificación son geniales y fáciles de usar, pero los predicados son lo que realmente hace que la búsqueda sea poderosa en Core Data. Mientras que las descripciones de ordenamiento le dicen a Core Data cómo deben ordenarse los registros, los predicados le dicen qué registros le interesan. La clase con la que trabajaremos es NSPredicate.

Comencemos buscando a cada miembro de la familia Doe. Esto es muy fácil de hacer y la sintaxis recordará a algunos de ustedes el SQL.

No hemos cambiado mucho aparte de crear un objeto NSPredicate invocando predicateWithFormat: y vinculando el predicado a la solicitud de búsqueda pasándolo como un argumento de un llamado setPredicate:. La idea detrás de predicateWithFormat: es similar a stringWithFormat: en que acepta un número variable de argumentos.

Ten en cuenta que la cadena de formato de predicado usa %K para el nombre de propiedad y %@ para el valor. Como se indica en la Guía de programación de predicados,%K es una sustitución de argumento variable para una ruta clave, mientras que %@ es una sustitución de argumento variable para un valor de objeto. Esto significa que la cadena de formato predicado de nuestro ejemplo evalúa para last == "Doe".

Si ejecutas la aplicación una vez más e inspeccionas la salida en la consola de Xcode, deberías ver el siguiente resultado:

Hay muchos operadores que podemos usar para comparar. Además de = y ==, que son idénticos en lo que se refiere a Core Data, también hay >= y =>, <= y =>, != y <>, y > y <. Te invito a experimentar con estos operadores para saber cómo afectan los resultados de la solicitud de búsqueda.

El siguiente predicado ilustra cómo podemos usar el operador >= para obtener solo registros de persona con un atributo age (de edad) superior a 30.

También tenemos operadores para comparación de cuerdas, CONTAINSLIKEMATCHESBEGINSWITH, y ENDSWITH. Vamos a buscar cada registro de persona cuyo nombre CONTAINS la letra j.

Si ejecutas la aplicación ahora, la matriz de resultados estará vacía ya que la comparación de cadenas distingue entre mayúsculas y minúsculas por defecto. Podemos cambiar esto agregando un modificador como ese:

También puedes crear predicados compuestos utilizando las palabras clave AND, OR y NOT. En el siguiente ejemplo, buscamos a cada persona cuyo primer nombre contenga la letra j y tenga menos de 30 años.

Los predicados también facilitan la obtención de registros en función de su relación. En el siguiente ejemplo, buscamos a cada persona cuyo nombre de padre es igual a Bart.

El predicado anterior funciona como se esperaba, porque %K es una sustitución de argumento variable para una ruta clave, no solo una clave.

Lo que debes recordar es que los predicados te permiten consultar el almacenamiento sin que sepas nada sobre el almacenamiento. Aunque la sintaxis de la cadena de formato del predicado tiene reminiscencias de SQL de alguna manera, no importa si el almacenamiento de respaldo es una base de datos SQLite o un almacenamiento en memoria. Este es un concepto muy poderoso que no es exclusivo de Core Data. El registro activo de Rails es otro buen ejemplo de este paradigma.

Hay mucho más en los predicados que lo que te he mostrado en este artículo. Si deseas obtener más información acerca de los predicados, te sugiero que tomes el punto más alto en la Guía de programación Predicate de Apple. También trabajaremos más con los predicados en los próximos artículos de esta serie.

Conclusión

Ahora tenemos una buena comprensión de los conceptos básicos de Core Data y es hora de comenzar a trabajar con el framework creando una aplicación que aproveche su poder. En el siguiente artículo, nos encontraremos con otra clase importante de la estructura de datos básicos, NSFetchedResultsController. Esta clase nos ayudará a administrar una colección de registros, pero aprenderás que hace bastante más que eso.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.