jueves, 2 de abril de 2015

CoreData: Compartir Modelo con Apple Watch

En este tutorial vamos a ver como una de las posibilidad que iOS ofrece para la persistencia de datos. Hablaré desde mi experiencia, y resolviendo los problemas que me he encontrado yo al usarla en mis aplicaciones con el objetivo de que si habéis dado con este tutorial os evite cometer mis errores al menos. Exactamente al final trataré como solucione un problema que me encontré a la hora de desarrollar la aplicación para el Apple Watch.


1. Introducción

Dentro de iOS encontramos múltiples métodos para la persistencia de datos. Entre ellos encontramos NSUserDefaults o mediante archivos del tipo Property List. Estos métodos resultan más que suficientes para la mayoría de aplicaciones pero tienen ciertas carencias. Las carencias de las que hablo son que permite trabajar con pequeños volúmenes de información, sin relaciones o dependencias y no permite para nada la realización de consultas.

Para aplicaciones que necesiten algún de estas funciones se nos ofrece CoreData.


2. Core Data


Core Data no es una simple base de datos sino que va mucho allá. Esta formado por una serie de clases, que en conjunto unas con otras, conforman un framework para persistencia avanzada.



Por ellos nos permitiría con muy poco esfuerzo crear modelos de datos, relaciones, validación de datos, y posibilidades de deshacer, rehacer y carga inteligente para mejorar el rendimiento de búsquedas. El trabajo que lleva acabo Core Data esta divido en 3 conceptos:


  • Modelado de Datos
  • Persistencia de los datos o guardar.
  • Recuperar o leer la información

Las 3 clases o elementos principales que conforman CoreData son:

  • NSManagedObjectModel: es el modelo de los datos, que propiedades tiene cada objeto, relaciones entre ellos ... Se ve representando como un archivo con extensión .xcdatamodeld.
  • NSManagedObjectContext: es el encargado de la lectura y escritura. Por ello es el más usado ya que nos da acceso a la creación de información así como realizar consultas.
  • NSPersistentStoreCoordinator: es que guarda en disco la información a través de una base de datos, por defecto SQLite.
En el tutorial veremos como se usa cada una de ellas pero nos centraremos en los  2 primeras por que la 3 es muy avanzada y de bajo nivel que en la mayoría de los casos no hay que modificar. 

3. Core Data en mi App

CoreData se puede añadir al crear un proyecto ya que se ofrece la plantilla, muy recomendable usar esta opción al menos para empezar. Y por otra parte se puede añadir a un proyecto ya existente con más que crear el fichero ".xddatamodeld" e crear las instancias de las 3 clases que hemos hablado antes. Ahora iremos entendiendo esto con un pequeño ejemplo. 

Vamos a irnos al a ventana de Xcode para crear un nuevo proyecto y seleccionamos Master-Detail Application,  y veremos que se nos permite poner que queremos usar CoreData. Por otra parte usaremos como lenguaje Swift. En caso de no conocer este lenguaje recomiendo mirarlo un poco antes. 



En caso de que se nos olvide darle a CoreData o ya tengamos una aplicación existente no hay que preocuparse. Xcode lo que hace es crear ciertas variables, archivos y configuración automáticamente para simplificarnos el trabajo. En caso de que se nos olvide pues tendremos que hacer:


  • Crear el archivo .xcdatamodeld
  • Añadir las variables y métodos en la clase AppDelegate (u otra aunque es recomendable aquí)



El nombre del  proyecto que he elegido es MyLeague, este proyecto lo usaré como ejemplo para ir desarrollando el tutorial. Una vez puesto esto y el resto tal y como la figura pasemos a analizar que código a generado automáticamente Xcode



Para empezar veremos la clase AppDelegate.swift para ver las propiedades y métodos creados. Bajando encontraremos las propiedades "lazy", estas son propiedades que se calcula su valor en el momento de que haga falta usarla para así mejorar el rendimiento. Se igualan a un bloque que devuelve una instancia de clase del tipo de la propiedad. 



El primero de ellos variable se denomina applicationDocumentsDirectory y es de tipo NSURL. Como su nombre indica es la url al directorio donde la aplicación guarda y almacena su información.




La propiedad managedObjectModel se encarga de apuntar al archivo que tenemos ..xcdatamodeld sencillamente. Como vemos sencillamente obtiene la URL que identifica al archivo y con ella crea la clase.



Esta propiedad, persistentStoreCoordinator, puede ser la más confusa, pero no es tanto. Se crea a partir del modelo y a continuación lo que hace es configurar que tipo de base de datos usará para realizar la persistencia. Como vemos será de tipo SQLite.


La siguiente propiedad denominado managedObjectContext es con la que más se trabaja como hemos comentado anteriormente. Su creación es sencilla solamente la inicializamos y le asignamos el coordinador que acabamos de ver.


Por último se crea una función que sencillamente realiza la persistencia de los datos si el contexto tiene cambios.



Para terminar con esta parte vamos a ver el archivo MyLeague.xcdatamodeld. Este fichero  almacena el modelo de datos que creemos. Veremos que para simplificarnos la vida Xcode tiene un entorno gráfico para crear el modelo permitiendo añadir:

  • Entidades y sus atributos
  • Relaciones 
  • Peticiones predefinidas (estas no las veremos ya que para empezar no es muy necesario)
En la siguiente imagen podemos ver el entorno gráfico para crear el modelo. En el he creado dos entidades Player y Team (jugador y equipo). 
El jugador tiene una atributo denominado "name", que es el nombre y una relación que indica al equipo que pertenece. Por su parte el equipo tiene un atributo y igual y una relación que indican que jugadores pertenecen al equipo. 

Los atributos pueden de cualquier tipo que aparece en la imagen inferior:


A parte del tipo de atributo se permite modificar diferentes propiedades como valor por defecto, y otros aspectos más avanzados que no comentaré como son "transient", "optional" o "indexed". En la imagen inferior se ve la interfaz que aparece para el caso de un atributo de tipo "string".


En cambio las relaciones son algo más complicadas ya que se indican de que entidad a que entidad van y que propiedad es su inversa. Ejemplo un jugador tiene una relación con un equipo (relación de uno a uno) pero un equipo tiene muchos jugadores (relación de uno a muchos). También puede haber relaciones de muchos a muchos. En el cuadro inferior se ve la interfaz que se abre cuando queremos modificar una relación en sí.




A parte de esto en el proyecto he realizado algunas modificaciones para que añada jugadores con nombre la fecha actual, una tontería pero válido para ir explicando.


Si ahora mismo nos fijamos en el proyecto que tenemos veremos que no tenemos clase asociada a la entidad "Player" o "Team". Esto ocurre por que realmente no es necesario crear subclases de NSManagedObject para poder trabajar con CoreData. Se puede trabajar con estas directamente accediendo a sus atributos a través de claves lo cual esta muy bien pero mucho mejor es referirse mediante clases específicas en el código.
Un ejemplo mediante clave sería: 

  • Crea el objeto: 

let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as! NSManagedObject

  • Se modifica la propiedad del objeto

newManagedObject.setValue("Name: " + NSDate().description, forKey: "name")


Para generar las clases relativas al modelo Xcode nos ofrece una opción en el menú Editor denominada Create NSManagedObject Subclass. Una vez le damos y seguimos los pasos nos crea archivos los cuales son las clases para cada entidad. Una vez creados nos vamos al modelo y le decimos que la clase referida a esa entidad es la que se ha creado. Y con eso ya tendremos listo. 

Ahora se puede realizar así:

  • Crea el objeto: 
let newManagedObject:Player NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) asPlayer


  • Se modifica la propiedad del objeto
newManagedObject.name = "Name: " NSDate().description

Vemos que es mucho más limpio. Además en las clases se puede añadir funcionalidad adicional referida a cada entidad. 


Una vez llegados hasta aquí voy a dejar el proyecto hasta este punto y me voy a centrar en detalle en cuestión que necesite para mi aplicación.

Os dejo el link del projecto de GitHub: MyLeagueV1.0
Link de descarga directa: MyLeagueV1.0

4. Compartir Core Data: App & extensión Watch App

Hace relativamente poco Apple ha introducido su nuevo dispositivo Apple Watch. Por ello me puse a hacer una pequeña aplicación para hacer lista de la compra, de artículos o lo que uno quiera. El caso es que para ello elegí Core Data. El problema fue cuando quise añadir la extensión para que también tuviera la función para el Apple Watch.

El problema es que no podía acceder desde el entorno de este a la clase que las variables referidas al Core Data. Y aquí explico la solución que encontré al problema. 

Pues bien la idea esta en crear una biblioteca o librería aparte que contenga nuestro modelo de CoreData así como todas las variables y clases necesarias. La clase principal de la librería será un "singleton" es decir una clase con una única instancia de modo que desde el propio entorno para la aplicación se pueda acceder a dicha librería y a la vez desde la extensión para el Apple Watch .

El primer paso será crear la librería en nuestro proyecto para eso desde Xcode seleccionamos File --> New --> Target, y se abrirá un menú en el cual elegimos la opción que aparecen en la imagen inferior.


Una vez se crea la librería podemos añadir en ella todo el código correspondiente a CoreData. 

Veamos mi ejemplo para ilustrar como se haría. En mi caso he nombrado al "Framework" como "DateServerKit". En él he los siguientes archivos:

  • DataAccess.swift: es la clase principal, se trata de un "singleton" por el cual podremos acceder desde ambos entornos al contexto y demás elementos de CoreData
  • iwshopping.xcdatamodeld: archivo que contiene el modelo de mi aplicación, en este caso se trataba de un aplicación para hacer lista de artículos
  • Item.swift y List.swift: clases autogeneradas con Xcode referidas al modelo simplemente. 



Analicemos la clase DataAccess ya que es la que tiene más complejidad para crearla. Lo primero que vamos a analizar es como hacer esta clase un "singleton". Existen varias formas iguales de válidas pero yo me he decantado por la que Apple aconseja:

public class var sharedInstance: DataAccess {
        struct Static {
            static var onceToken: dispatch_once_t = 0
            static var instance: DataAccess? = nil
        }
        dispatch_once(&Static.onceToken) {
            Static.instance = DataAccess()
        }
        return Static.instance!
    }

El "singleton" es un método público de clase por el cual siempre accederemos a la misma y única instancia de dicha clase. 

El siguiente paso a tener en cuenta es que debemos definir un App groups (con la cuenta de apple en su página web para desarrolladores) el cual define un lugar de almacenamiento compartido podríamos decir de modo que tanto la aplicación como la extensión pueden acceder a ese contenedor. 
Para crear el App Groups vamos a la página de Apple Developer y en Certificates, Identifiers & Profiles está el apartado. En la imagen vemos una captura para que quede más claro.

La página para crear el grupo es: AppleDeveloperMemberCenter



 El nombre que se le dan a los App Groups son del tipo group.ejemplo.nombregrupo. En mi caso usé el que se ve en la imagen. 

Una vez hemos tenemos ya el grupo definido es necesario tanto en la aplicación con en la extensión añadirlo en el apartado capacidades. En la imagen lo vemos más claro:



Una vez todo preparado hay que añadir en el "singleton" todo la lógica de CoreData que hemos explicado al principio añadiendo una novedad referida al grupo.

En el método que indica la dirección al contenedor donde se almacena la información en este caso debemos tener en cuenta al grupo.



Los grupos también sirve para usar la clase NSUserDefault para guardar determinados valores e información entre las aplicaciones.  Un ejemplo de uso:

var defaults: NSUserDefaults? = NSUserDefaults(suiteName: "group.com.SaulMorenoAbril.iWShopping")

Una vez lo obtenemos ya se puede acceder y escribir valores. 


Para terminar dejo mi framework: DateServerKit






0 comentarios:

Publicar un comentario