Persistencia de datos en iOS y Google Web Toolkit

En esta tercera entrega se explica brevemente como crear un modelo de datos usando XCode (internamente la gente de la manzana se ha apropiado de SQLite para hacerlo, han aprendido de la comunidad otra vez,al igual que en la época NeXT jeje) y cómo crear un servidor en la Google App Engine con las herramientas de Google Web Toolkit que nos proporcionará mediante servicios web la comunicación con una base de datos externa que desplegará datos a todos los dispositivos…lo que quiere decir es que puedes replicar un cambio desde cualquier lugar hacia todos a la vez de forma atómica…apasionante no?
Como no todo en esta vida es un camino de rosas, tratar con punteros y tipos de datos complejos tiene sus cosas, en XCode, o mejor dicho, con la versión de Objective C privativa que usamos para compilar nuestras aplicaciones para los dispositivos, tratar con la memoria es como jugar con granadas, desde que lanzas la granada al aire (reservas memoria) hasta que la recoges (liberas memoria), la parábola que surca en el aire podría ser un tanto extraña, también puede perderse la granada o ser reemplazada, meterse entre nubes o chocar con un avión que pasa jaja…con lo cual, nuestra tarea consiste en tratarla como a una mujer,delicadamente…para que no nos explote en la cara…Jugando con la memoria
Conceptos básicos para almacenar datos en iOS:

Core Data Foundation

  • Es un framework que garantiza la persistencia de los datos de nuestra aplicación
  • Genera una abstracción con clases que facilitan la implementación final del almacenamiento
  • Conecta la parte lógica con el modelo físico (ficheros, etc.)

Core Data Stack

  • El objeto que utilizaremos para gestionar colecciones de objetos es el Managed Object Context, también conocido en la jerga NeXT como NSManagedObjectContext, y representa el espacio de un único objeto, es decir, vamos  a utilizar este objeto para recuperar todo lo que tiene que ver con una entidad (una entidad es una tabla de la base de datos, también puede contener atributos en relaciones cuyos tipos sean otras entidades o tablas)
  • Con Managed Object Model (esquema de base de datos) guardaremos una colección de descripciones de entidades (nombres de tablas) pertenece a la clase NSManagedObjectContext  que relaciona Managed Object Context con el siguiente objeto que hace de puente:
  • Persistent Store Coordinator: guarda una colección de almacenes (Persistent Object Stores)

    en un conjunto de ficheros de datos persistentes (los ficheros de la base de datos, como en MySQL), su clase es NSPersistentStoreCoordinator y asocia los objetos de la aplicación con los registros de la BD.

Guía de uso del Modelo de Datos de iOS:

  1. Para hacer una consulta primero necesitamos crear las tablas, para ello lo que hacemos es crear un proyecto de tipo datos o bien añadimos un fichero al actual proyecto que sea XCode -> Core Data -> Data Model, en la pantalla de creación de entidades añadimos una tabla o entidad y la llamamos “Event”, por ejemplo. Dentro añadimos sus atributos con distintos tipos. Veréis que podéis usar un tipo “Transformable” que sirve para guardar Arrays, siempre y cuando los tipos que guarde el array sean soportamos por el protocolo del modelo de datos, además podemos crear relaciones como atributos que se conectan con otras entidades, aunque esto es más complejo, por ahora sólo veremos los casos simples. (ver más información >>)
    Podéis ver que el editor trae un soporte para la vista de un bonito Diagrama de Entidad/Relación de nuestra BD.
    Una vez creado el modelo vamos a New file -> Core Data -> NSManagedObject subclass (si lo que queremos es una clase que se encargue de realizar todas las operaciones setter & getter por nosotros y así asociemos el contenido al modelo de datos fácilmente con funciones, aunque también podemos hacerlo aún más automático con un framework como este: Fremont)
  2. Para crear objetos es necesario invocar a NSEntityDescriptor con el tipo de objeto y el contexto, veamos un ejemplo:(os recomiendo siempre escribir el código el inglés ;) . Supongamos que nuestra tabla (modelo de datos) event tiene los campos, longitud y latitud de tipo Float, un título (NSString -> String en Modelo de Datos), y una fecha de tipo date:
    //Crear y configurar una instancia de una entidad Event
    - (void) createEvent {
    Event *event = (Event *) [NSEntityDescription insertNewObjectForEntityForName:
    @"Event" inManagedObjectContext: managedObjectContext];
    //Crear un tipo de dato coordenada para mapas de google, así nos acostumbramos a usarlos:
    CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(37.123123, -3.321321);
    //Ahora lo tenemos muy fácil ,sólo tenemos que usar los setters y getters generados por XCode:
    [event setLatitude:[NSNumberWithFloat:coordinate.latitude]];
    [event setLongitude:[NSNumberWithFloat:coordinate.longitude]];
    [event setCreationDate:[NSDate date]]; //"date" es la fecha de hoy
    [event setTitle:@"Mi primer evento"];
    }

    y eso sería todo el código, ahora pasamos a grabar los datos para hacerlos persistentes

  3. Para guardar un objeto se utiliza el método savede NSManagedObjectContext, si ocurre algún error se guardará la información del mismo (el motivo por ejemplo) en la variable NSError dispuesta a tal efecto.
    NSError *error;
    if ([managedObjectContext save:&error]){ 
    //prestar especial atención al ampersand...
    //referencia de memoria!
    //Gestión del error aquí
    }

    Podemos intentar capturar errores con una captura de excepciones (@try { } @catch (NSException *exception) { } @finally { } ) pero esto no es recomendable, además de que debemos recordar la cadena de respondedores, aquí se aplica el mismo cuento y tendríamos que ir hacia arriba en la lógica de la programación para capturar la verdadera excepción de la pila de llamadas…cosa que es bastante tediosa, por eso es mejor pensar las cosas bien y hacerlas mejor jeje, con la práctica todo se consigue ;)
    Fetch Request -> execute!

    Si queremos recuperar el objeto no tenemos más que usar, como se hace en php y mysql una petición tipo “fetch”, es decir, con NSFetchRequest especificamos la entidad, aquí tenéis un ejemplo completo.
    En resumidas cuentas, hay que crear un objeto NSFetchRequest, reutilizamos el objeto de la descripción de una entidad y asociamos esta al primero con setEntity:

    NSFetchRequest *request = [[NSFetchRequest alloc] init]; 
    //cuidado que esto no se libera. lo hace sólo...:O
    NSEntityDescription *entity = [NSEntityDescription 
    entityForName:@"Event" inManagedObjectContext: managedObjectContext];
    [request setEntity:entity];
    //Para realizar un ORDER BY fecha típico usamos un descriptor de ordenación:
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:
      @"creationDate" ascending:NO];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    [request setSortDescriptors:sortDescriptors];
    //ahora sí, liberamos los descriptores
    [sortDescriptor  release];
    [sortDescriptors release];
    //Con todo configurado, vamos a ejecutar la consulta y
    // guardar el resultado en una matriz modificable:
    NSError *error1;
    //Atención al parámetro "mutableCopy"!!
    NSMutableArray *results = [[managedObjectContext 
       executeFetchRequest:request &error1] mutableCopy];
    if ( results == nil ){
        //Manejar el error!
    }
    //Para borrar necesitamos un NSError igual que cuando guardamos con save
    NSError *error2;
    [managedObjectContext deleteObject:objetoEventoParaBorrar];
    if (![managedObjectContext save:&error2){
     //error...
    }

    Nunca os asustéis de un error como éste: significa que no habéis asociado bien el tipo de dato al diccionario usado para realizar una inserción en masa, ánimo!

Que los robots escriban código por nosotros: Servicios Web

Recordamos del curso de servicios web que escribir XML y código de un servicio web no es tarea de humanos, para eso existen frameworks que harán el trabajo duro por nosostros, Fremont es la parte cliente, en Objective C, para la parte del servidor tenemos los transformadores que ya vimos, BPEL, etc.

Gracias al uso de un servidor asociado a Google App Engine, crearemos un conjunto de servicios usando Google Web Toolkit 2.3.0 , que una vez probados en red local podremos desplegar en el servidor Java de GAE asociado a nuestra cuenta de usuario.

Pasos para la creación de un servidor de datos por medio de servicios web con Eclipse:

  1. Descargar el IDE Eclipse (Hellios SR2 por ejemplo) y el plugin para GWT.
  2. Crear un proyecto de prueba y ejecutarlo
    Ir a File -> New – > Project … -> Google -> Web Application Project . Le dáis un nombre al proyecto y al package sencillos, os recomiendo usar un área de trabajo nueva para no mezclar, aseguraos de que la opción de Google Web Toolkit está marcada y que estamos usando el SDK de GWT – 2.3.0 asó como el Google App Engine con su SDK (el que sea, en mi caso el 1.5.0) y marcad que genere el código de ejemplo para que todo fluya :) . Si todo ha ido bien debéis poder desplegar la aplicación en vuestra cuenta de google sobre el App Engine y ver el producto generado…ya podéis ser un 3% más felices hoy jeje
  3. Crear un paquete de servicios para nuestro servidor de aplicaciones de iPhone.
  • Pasos:
    • Crear paquete
      En el directorio src pinchamos con el segundo botón y hacemos New -> Package ,de nombre le damos servicios_iOS, por ejemplo. El primer servicio que vamos a crear es un servicio simple, que muestre una lista de eventos, pero antes debemos crear el modelo de datos que ha de persistir (guardarse) en el Data Store de Google Web Toolkit.
    • Crear modelo de datos
      Dentro de nuestro paquete hacemos click con el botón secundario del ratón y -> New -> Class, de nombre “Evento”, claro ,jeje…me gusta escribir todo el código de un servidor en inglés ya que es algo que veremos poco y puede que solamente nosotros…como opciones pues, es una clase pública, etc. Dentro de la clase añadimos ,como os imaginaréis,
      private Key id; private String titulo; private float latitud; private float longitud;
      y luego usaremos los generadores de setters y getters que tanto nos gustan de Java en Eclipse, así como los constructores.

      Hay un caso curioso, como veréis he puesto Key, en lugar de un int o un Long para la llave principal, esto es debido a que si queremos usar el objeto como algo persistente y almacenable dentro de otra clase, esta llave le permite indexar al objeto por lo que puede serializarse, convertirse en una cadena y quedar guardado en las bases de datos de Google.
      A cada campo le debéis añadir los protocolos antes de su declaración “@Persistent“, y a la llave principal le añadimos @Primary Key , y también: @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY).
      A la clase Evento además le añadimos el siguiente prefijo: @PersistenceCapable(indetityType = IdentityType.APPLICATION)
    • Añadir complejidad al modelo de datos
      Para aquellos que no se contentan con un modelo de datos simple y quieren asociar una lista de cosas (vaya usté a saber qué más cosas…) pueden usar el prefijo: @Persistent(embedded=”true”) para variables del tipo private List<TipoDeDatosPropio> miListaDeCosas; Sin olvidarse,claro, de que el tipo de datos propio ha de llevar nuestro querido prefijo @PersistenceCapable(indetityType = IdentityType.APPLICATION) y que ha de contener una llave o Key id para poder serializarse y empaquetarse dentro de un atributo persistente de otra clase persistente
    • Crear servicios que usen el modelo de datos
      Tenemos un modelo donde almacenar datos, utilizaremos un fichero HTML en el directorio /war/ sencillo donde colocar un formulario con los campos título, longitud,latitud, fecha y cuya acción (o action, con method=”POST”) irá a “/nombreProyecto/nombreAccionServicio”.
      Para crear el servicio que atenderá la petición vamos a nuestro paquete y -> new -> class -> “nombreAccionServicio”.  A la clase, le ponemos el prefijo @ServiceName(value=”nombreAccionServicio”) y hereda (con la palabra clave extends) de la clase HttpServlet, de modo que debemos sobrecargar la función
      public doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, DatastoreFailureException { //… },
      para que atienda la petición enviada desde el fichero HTML por el método POST. Igual que en PHP.
      Importante que la respuesta se haga de una manera parecida a esta:

      resp.setContentType("text/html");
      PrintWriter out = resp.getWriter();
      out.println("< !DOCTYPE HTML PUBLIC \"-W3C//DTD....."); //etc

      Una vez que hayamos creado nuestras inicializaciones de datos de nuestro modelo (instanciado las clases oportunas), debemos guardarlos usando un gestor de Persistencia, lo que viene a ser una clase de JDOHelper.getPersistenceManagerFactory(“transactions-optional”); que por medio de la función makePersistent(clase_instanciada) se almacenará (después debemos hacer un close(), claro jeje)

    • Añadir las rutas al war/WEB-INF/web.xml –> este es el enrutador de acciones, asocia un nombre de ruta en una url (con un patrón) a un servicio web (con una clase que extiende de HttpServlet). Para ello,abrimos el fichero y creamos dentro del web-app un hijo servlet que especifica la asociación con el programa usando la palabra clave servlet-name:nombreServicio y servlet-class: nuestra clase nombreAccionServicio.
      Por último, para el mapeado, es decir, la forma en que un patrón o expresión regular asocia una ruta url a nuestro servlet, añadimos un hijo de “web-app” llamado servlet-mapping con el mismo servlet-name:nombreServicio y url-pattern:/nombreAccionServicio
  • Repetir operación para crear servicios que generen listas de datos, en nuestro caso nos hará falta que se genere un listado XML de todos los <eventos> con sus etiquetas para cada campo que tiene el modelo de datos. Para ello, añadimos una función a la clase Evento que genere la cadena XML como un string en UTF8 y un Servlet que la muestre por pantalla, en este caso la cabecera ha de ser text/xml ;)
  • Más información en este tutorial >>

Análisis sintáctico: XML – Servicio Web – iOS

Como ya hemos visto antes el funcionamiento de la persistencia de datos en iOS con un Modelo de Datos, pasaré directamente al análisis de un XML, aunque esto debería hacerse automáticamente con un framework como Fremont, lo importante es que os quedéis con que las clases que permiten estas tareas son NSURL, NS/Mutable\URLRequest,y NSURLConnection. Estas descargan los datos y podemos realizar el análisis sintáctico con varios tipos de “parser”, SAX: envío de notificaciones a medida que el analizador sintáctico va leyendo la cadena XML recibida por NSURL, o bien DOM:  es un analizador que lee toda la cadena XML y construye su representación completa. Para utilizar estos analizadores debemos incluir al proyecto el fichero libxml2 de las librerías del SDK (tanto SAX como DOM), o bien NSXMLParser (sólo SAX). Aunque existen alternativas como TBXML, TouchXML, KissXML, TinyXML, GDataXML, etc. –> ver comparativa >>

Pasos para utilizar NSXMLParser:

  1. Crear el parser con la información recibida de NSURL.
    NSXMLParser *parser  = [[NSXMLParser alloc] 
    initWithData:xmlData];
     //recibidos por NSURL
  2. Asignar un delegado (delegate), una clase que se encarga de recibir los eventos,i.e., que hereda de <NSXMLParserDelegate>
    [parser setDelegate:self];
  3. Comenzar a parsear con [parser parse];

A partir de aquí, se trata de usar los eventos del delegado (eventos de NSXMLParserDelegate) que son: didStartElement -> empieza un elemento, didEndelement, didStartDocument, etc. de forma que al principio del documento inicializamos los datos (un array modificable, por ejemplo), y cada vez que encuentra un elemento de tipo titulo pues añade un elemento al array y luego al terminar un elemento de tipo evento, pues añade el array de propiedades de un evento al array de eventos…fácil…Importante implementar también la función:

- (void)parser:(NSXMLParser *) parser parseErrorOccurred:
  (NSError *)parseError;
ya que nunca se sabe qué errores se pueden cometer, por ejemplo que el documento no llegue completo, y la aplicación pende de un hilo por una mala gestión de este tipo de errores…algo que no da buena imagen por lo que hay que pulir estos detalles…

Uso de caché

La utilización de caché tanto para datos persistentes como para datos dinámicos es importante a la hora de realizar aplicaciones para móviles ya que está muy penalizado el uso de conexiones a Internet, es más rentable llegar a un equilibrio de carga de datos entre el servicio web y los usuarios de los dispositivos, por eso os presento las dos tareas a realizar en un proyecto serio…

Caché de imágenes con ASI Http Framework

En esta dirección encontraréis un framework sorprendente para realizar tareas con análisis sintácticos de XML, descarga de datos a través de urls, caché, etc.

Es una maravilla ver como funcionan los ejemplos, hay uno,especial de caché en el que se dispone una serie de descargas de imágenes y a cada una de ellas se le asocia un puesto en una clase cola-de-espera que tiene asociada una barra de progreso por lo que va actualizando el estado conforme se van descargando los datos…impresionante :)

Caché de datos con SQlite

Lo que podéis hacer es utilizar una caché para guardar la información en un modelo de datos, durante unos días en el iDevice, pasados esos días, vuelve a sincronizarse con el servicio de Google App Engine y reescribimos toda la estructura de datos del programa con nueva información, así nos ahorramos realizar peticiones al servidor continuamente, lo que ralentizaría mucho la carga y si la aplicación es muy usada puede generar un cuello de botella que ha de evitarse.

Para hacer esto nos viene bien los datos de configuración de un usuario, lo veremos en la próxima entrega del curso de aplicaciones para iOS, aquí mismo.

Recordar que podéis utilizar recursos como http://wiki.gnustep.orghttp://stackoverflow.com/ para solucionar vuestras dudas tanto con Objective C como con Java Google App Engine y GWT.

En cualquier caso, los ejercicios de esta entrega están claros cuáles son: desplegar una aplicación en GAE con GWT y pasar los datos a un modelo de datos de XCode (SQlite)…hay cientos de tutoriales en internet sin embargo si tenéis cualquier duda, mandadme el fichero y lo intentaré corregir.

<< Volver al curso de programación de aplicaciones de iOS | Siguiente lección: Configuración y traducción de una aplicación de iOS >>

Artículos relacionados:

Comentarios no permitidos.

footer
jbelon © | sitemap.xml