Core Data Luis Montesano & Ana Cristina Murillo Core Data ● ● ● ● Crear un grafo de objetos en memoria con propiedades de persistencia Proporciona un interfaz visual para crear el grafo de objectos (tipo base de datos relacional) Crea un mapa entre los datos persistentes y el grafo de objetos Permite: – Crear, buscar y eliminar objetos (registros de la BD) – Usar siempre objetos (en lugar de una API de C) – Usar notación tipo properties para acceder a los datos Core Data Stack Core Data ● Permite un montón de operaciones ● Nos vamos a centrar en – Crear entidades, atributos y relaciones – Utilizar las clases de busqueda que proporciona iOS – Integrarlo en un tableViewController Core Data Visual Tool Core Data Visual Tool Utilizar Core Data ● ¿Como accedemos a las tablas creadas? – NSManagedObjectContext: ● ● – es el espacio de objectos en el que guardamos y recuperamos los objetos (datos) Juega un papel central (life cycle, unro/redo, validation...) NSManagedObject: ● ● ● Es la representación en objeto de un registro de la base de datos (Entity) Es el modelo en un MVC Esta siempre asociado a un contexto NSManagedObjectContext ● La manera facil de crear y usar: – Se incluye al crear el proyecto soporte para CoreData (marcar la casilla “Use core Data for storage”) – En el applicationDelegate se crea ● ● una @property llamada managedObjectContext Código para gestionar el contexto NSManagedObject ● Una vez que tenemos el contexto podemos añadir objetos al mismo // Create and configure a new instance of the Event entity. NSManagedObject *event = (Event *)[NSEntityDescription insertNewObjectForEntityForName:@"Event" inManagedObjectContext:managedObjectContext]; ● El contexto es una objeto que se utilizará en multiples controladores – Se debe compartir – Lo habitual es inicializarlos con algo tipo: - initInManagedContext:(NSManagedObjectContext *)context; Acceder a los datos - (id)valueForKey:(NSString *)key; - (void)setValue:(id)value forKey:(NSString *)key; ● Los dos métodos nos permiten recuperar o escribir los datos de un NSManagedObject – La clave es el nombre de un atributo (dado en la herramienta visual) – Value es lo almacenado (o lo que se va a almacenar) ● Son siempre objetos (incluso los números) – – – ● NSManagedObject cuando es una relación 1 a 1 NSSet para 1 a N NSData para datos binarios Nil cuando esta vacio Acceder a los datos - (id)valueForKey:(NSString *)key; - (void)setValue:(id)value forKey:(NSString *)key; ● ● ● El sistema se encarga de generar las sentencias de acceso a la BD Las busquedas se realizan de forma perezosa cuando se accede a/escriben los datos Necesitamos guardar los cambios - (BOOL)save:(NSError **)errors; - (BOOL)hasChanges; – Métodos de NSManagedObjectContext – Frecuentemente (cuando se han hecho un conjunto de cambios) – NSError ** para recuperar errores (si NULL, no salva nada despues de un error) - (void)saveChangesToObjectsInMyMOC:(NSManagedObjectContext *)context { NSError *error = nil; if ([context hasChanges] && ![context save:&error]) { NSLog(@“Error! %@, %@”, error, [error userInfo]); abort(); // generates a crash log for debugging purposes } } Derivando NSManagedObject ● ● Hasta ahora hemos usado siempre objetos genéricos – No hay control de ningun tipo en compilación – Basado en claves (cadenas) en lugar de en @properties Se puede crear una subclase de NSManagedObject – Con properties para cada atributo – Xcode genera el código por nosotros ● Dos formas: completa y por atributo Copiar codigo para atributos DEMO con Xcode de creación de tablas y de las clases correspondientes ● La clase contiene properties para cada atributo – ● ● Siempre objetos (NSNumber, NSData, ...) La implementación no tiene @synthesize, sino @dynamic – Indica que se hará en tiempo de ejecución – NSManagedObject convierte en tiempo de ejecución los get/set en valueForKey: and setValue:ForKey: Se accede a los registros con la notación de properties Event *myEvent = [NSEntityDescription insertNewObjectForEntityForName:@“Event” inManagedObjectContext:managedObjectContext];]; NSNumber *myLon = event.longitude; event.latitude = [locManager whatEverMsg: ...] Consultar en Core Data ● ● La entidad principal que proporciona soporte es NSFetchRequest Para crear una, debemos contar con: – NSEntityDescription: descripción de la entidad en la que buscar (obligatorio) – NSPredicate: criterios para seleccionar dentro de una entidad (opcional, por defecto busca todos) – NSSortDescriptors: Conjunto de funciones que indican como ordenar los objetos encontrados (opcional, aleatorio por defecto) – Configurar la busqueda: número máximo de objetos, tiempo máximo para la busqueda (opcional, todos) Crear un NSFetchRequest NSFetchRequest *request = [[NSFetchRequest alloc] init]; request.entity = [NSEntityDescription entityForName:@“Event” inManagedObjectContext:ctxt]; request.fetchBatchSize = 20; request.fetchLimit = 100; request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor]; request.predicate = ...; ● Obtener la descripción de la Entity en la que vamos a buscar ● Límites de busqueda ● ● ● – Tamaño del grupo de datos a recuperar (20 cada vez) – Número máximo de grupos Creación de un NSArray para guardar los descriptores de ordenación (a continuación) Creación del predicado (a continuación) Las NSFetchRequest se pueden guardar, se pueden generar con parámetros que se sustituyen ... NSSortDescriptor NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@“latitude” ascending:YES selector:@selector(compare:)]; ● Los elementos del NSArray resultado de la busqueda estarán ordenados de acuerdo a los descriptores – pares de atributos/funciones – multiples pares para ordenar en varios criterios (apellido y nombre) – Método por defecto compare: (en ese caso no es obligatorio ponerlo) NSPredicate ● Es el Select de Core Data ● Indica que objetos queremos recuperar con la busqueda ● Se puede construir de varias maneras – Con cadenas: NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(lastName like[c] %@) AND (birthday > %@)", lastNameSearchString, birthdaySearchDate]; ● Existen muchos tipos de expresiones @“uniqueId == %@”, [event objectForKey:@“id”] @“%@ in tags”, (NSManagedObject *) // tags is a to-many relationship @“viewed > %@”, (NSDate *) // viewed is a Date attribute in the data mapping @“name contains[c] %@”, (NSString *) // matches the string in name attribute case insensitively – Componiendo predicados: NSArray *array = [NSArray arrayWithObjects:predicate1, predicate2, nil]; NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:array]; Ejecutar la busquda NSFetchRequest *request = ...; // Create the request: Entity, sort descriptors, predicate NSError **error = nil; NSArray *results = [mangedObjectContext executeFetchRequest:request error:&error]; NSManagedObject *event = [results objectAtIndex:0]; for (Event *event in results) { ... } // Fast enumeration Event *event = [results lastObject]; // solo una respuesta ● Es el NSManagedObjectContext el encargado de la busqueda – Devuelve nil si hay un error – Devuleve los datos en un vector (vacio si no hay datos que cumplan con el predicado) – Pasar NULL en error: si no se va a consultar los errores Eliminar objetos [managedObjectContext deleteObject:(NSManagedObject *)anObject]; ● ● Simple, pero hay que tener cuidado con las relaciones entre objetos y como eliminar afecta a estas relaciones (ver documentación) Además existen multitud de otras funcionalidades (ver documentación) Integración en una TableView ● ● Tenemos todo lo que necesitamos para poder rellenar una tabla No obstante, existe una clase que simplifica este procedimiento. NSFetchedResultsController – Se declara en el controlador de la vista tabla – Gestiona las busquedas y formatea de acuerdo a la vista tabla (número de secciones, filas por sección, celda para cada índice...) – Tiene un protocolo definido (opcional) NSFetchedResultsControllerDelegate que permite recibir notificaciones de cambios en el contexto que afecten a la visualización – Tiene caché (opcional) para mantener los resultados de las busquedas Crear un NSFetchedResultsController ● Se crea como instancia (normalmente) del table view controller ● Se inicializa con: ● – Una fetchRequest – Un managed object context – [Opcional] claves para particionar los datos en secciones (nil para una única sección) – [Opcional] nombre del fichero caché (nil dehabilita la caché) Una vez creada, la busqueda se ejecuta con performFetch: Ejemplo NSManagedObjectContext *context = <#Managed object context#>; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // Configure the request's entity, and optionally its predicate. NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"<#Sort key#>" ascending:YES]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; [sortDescriptors release]; [sortDescriptor release]; NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:@"<#Cache name#>"]; [fetchRequest release]; NSError *error; BOOL success = [controller performFetch:&error]; Métodos del DataSource Delegate - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [[self.fetchedResultsController sections] count]; // Asumimos que fetchedResultsController es una propiedad } - (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section { id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section]; return [sectionInfo numberOfObjects]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = <#Get the cell#>; NSManagedObject *managedObject = [fetchedResultsController objectAtIndexPath:indexPath]; // Configure the cell with data from the managed object. return cell; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section]; return [sectionInfo name]; } - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { return [fetchedResultsController sectionIndexTitles]; } - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { return [fetchedResultsController sectionForSectionIndexTitle:title atIndex:index]; } Métodos del Delegate - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { // Inspeccionar type y actuar en consecuencia switch(type) { case NSFetchedResultsChangeInsert: ... } } - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { [self.tableView beginUpdates]; } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { // Inspeccionar type y actuar en consecuencia switch(type) { case NSFetchedResultsChangeInsert: ... } } Demo en Xcode Nos hace todo lo que hemos visto!!!