Home » Guías » CloudKit (I), persistencia en la nube
CloudKit I

CloudKit (I), persistencia en la nube

Como ya comentamos cuando hablamos de cómo hacer apps y juegos para el Apple TV, tvOS no tiene almacenamiento local porque este se gestiona como una caché. Quiere decir que, aunque pensemos que podemos grabar en la zona de disco como hacemos usualmente, existe una muy alta probabilidad que cuando vayamos a por el dato, este luego no exista porque el sistema lo ha borrado.

Porque tvOS vive en la red, y como tal, necesita de ella para la persistencia. ¿Persistencia? Sí, la facultad de un dato de permanecer inalterado y almacenado en un lugar para su posterior consulta.

Y esa es una de las partes esenciales que tiene CloudKit, la plataforma de servicios en la nube para nuestras apps que Apple pone a nuestra disposición. Un servicio completamente gratuito para cualquier app, que empieza a tener un coste cuando las cifras de esta son claramente indicativas que obtenemos (o podemos obtener) una monetización importante de la misma.

De hecho, estamos muy acostumbrados a usar CloudKit, pues apps de iOS y OS X como Fotos, Notas y muchas otras utilizan CloudKit para conseguir la sincronización de los datos a través de diferentes plataformas. Y de hecho, nosotros también podríamos, lógicamente.

¿Por qué CloudKit?

Porque es fácil, potente, escalable y además, no nos limita a iOS, OS X o tvOS. CloudKit nos permite usar también implementación para web, de forma que podemos ofrecer acceso a los mismos datos que se hayan generado en nuestras apps, a través de portales que implementen de igual forma CloudKit.

Para poder activarlo, esta vez sí, debemos tener una cuenta de desarrollador de pago y poder acceder al punto neurálgico de CloudKit: su dashboard o panel de control, al que podemos acceder en este enlace. Este, nos permitirá controlar diversos aspectos como los registros de datos, números de usuarios que tengamos, uso de los servicios e incluso controlar el coste en caso que sobrepasemos los límites gratuitos del servicio.

Pero antes de poder acceder al mismo, hemos de tener, al menos, una aplicación activada para usar CloudKit. Y para ello solo tenemos que ir a las propiedades de nuestro proyecto, pulsar en la pestaña Capabilities donde podemos activar o desactivar determinadas funciones para la misma como el uso de Game Center o las compras integradas y activar iCloud.

Activación CloudKit

Marcamos como servicio CloudKit, dejamos puesto el contenedor por defecto, que dependerá del bundle identifier que tengamos en nuestra app, y tras esperar unos minutos a que se nos active podremos pulsar en el botón CloudKit Dashboard que nos abrirá un navegador que nos pedirá nuestro usuario del programa de desarrolladores para acceder al portal.

Cloudkit Dashboard

Una vez dentro, estaremos en la parte de desarrollo, no en producción. En ella tenemos varias opciones, primero los tres tipos de esquemas:

  • Record Types: Registros que nos permitirán almacenar datos como en una base de datos, o un diccionario donde definimos el tipo y el nombre de cada uno de los componentes del mismo.
  • Security Roles: Una vez tengamos creados tablas de registros, podemos definir roles de seguridad para ellos. De esta forma, si creamos una tabla para usuarios, podemos crear roles que permitan crear registros en esta tabla, leerlos o escribirlos.
  • Suscription Types: En ocasiones, si nuestros datos pueden ser modificados desde diversos sitios, puede que los datos no hayan cambiado. En esta pestaña podemos crear suscripciones a cambios, de forma que si hay un cambio en algún dato en función de una comprobación, nuestra app recibirá una notificación por la que sabrá que tiene que consultar de nuevo porque ha habido cambios en los criterios que se establecieron.

Registros y tablas

En este primer paso, vamos a ver cómo crear una tabla de registros. Vamos a Record Types y vemos que tenemos una tabla Users ya creada, así que creamos una nueva pulsando el + encima de Users y junto a la papelera. Una vez hecho esto se nos crea una nueva tabla, a la que podemos dar nombre y luego podremos crear tantos campos (fields) como queramos pulsando en Add Field….

Agenda 01

Cada vez que creemos un campo nuevo tendremos que decirle de qué tipo es:

  • Asset: Un fichero asociado a un registro pero que se almacena de manera independiente. Tiene un límite de 1MB.
  • Date/Time: Un dato de fecha y hora.
  • Int(64): Número entero de 64 bits.
  • Double: Número doble.
  • Bytes: Un buffer de bytes que se almacena en el propio registro.
  • String: Una cadena de texto
  • Location: Datos geográficos de localización.
  • Reference: Una referencia a otra tabla que tengamos creada para construir relaciones entre ellas.

Además de estos tipos básicos, también podemos hacer lists de las mismos (listas) que no son otra cosa que arrays de dichos tipos que nos permiten almacenar más de un dato a la vez dentro de un solo registro, y no usar una tabla separada para ellos.

Agenda 02

Para nuestro caso vamos a construir una tabla muy sencilla de una Agenda con campos Nombre, Apellidos y Telefono. Los dos primeros serán String y el último un número Int(64). Todos los cambios que hagamos, debemos validarlos pulsando abajo a la derecha en Save. Además, si queremos, podemos borrar el campo StringField que crea por defecto sin ningún problema, pulsando en la pequeña x a la derecha del nombre del campo cuando ponemos el ratón sobre el mismo.

A la derecha del nombre de la tabla veremos que tenemos una serie de checks que en este caso representan los índices que tendrán los campos que creamos: Sort, Query y Search para los campos de tipo String y Sort y Query para los numéricos. Estos índices nos permiten buscar (Search), consultar (Query) u ordenar los datos (Sort) para cada campo y se marcan de manera automática. Es conveniente que los dejemos.

Bases de datos públicas o privadas

Llegados a este punto, tenemos que entender algo muy importante: la diferencia entre base de datos pública y privada. La primera está pensada para datos no sensibles unidos al usuario (cualquier dato que en nuestra sea irrelevante a nivel de privacidad), mientras que la base de datos privada es una zona donde almacenamos datos sensibles. Cada cuenta iCloud que use la app tiene esta organización y hemos de saber muy bien dónde guardar cada dato.

Nuestras tablas pueden tener registros en una u otra base de datos (pública o privada) pero cuando consultemos tenemos que saber muy bien en cuál de ellas lo hacemos porque si estamos en la privada los de la pública no estarán y viceversa.

En este caso vamos a ir a la base de datos pública, que está en la opción Public Data y en Default Zone. Una vez allí, a la derecha, vemos que podemos crear registros de datos que nos sirvan de ejemplo pulsando en New Record, siempre teniendo presente que tengamos seleccionada dicha tabla. Podemos ver la selección arriba, a la derecha del nombre de nuestra app, en la zona azul. Si tuviéramos más de una tabla, podríamos desplegar y elegir.

Creamos un par de registros de ejemplo rellenando los datos convenientemente. Vemos que podemos establecer un nombre para el registro o dejar el auto-generado, y luego rellenamos los datos. Por cada registro de nuestra tabla se creará una posición bajo el nombre de la tabla. Pulsando en ella podremos navegar por los datos y en la parte superior, donde dice Sort by Apellidos podemos modificar la ordenación de los datos en varios parámetros, según los índices que tiene la propia tabla.

Un poco de código en Swift 2

Y ahora, manos al código. En cualquier clase donde queramos usar CloudKit es importante que tengamos el import que nos permitirá usar la implementación correspondiente.

Luego, también podemos tener varias opciones de constantes a usar, al estilo singleton de Objective-C. De hecho, podemos definir la constante que nos permita acceder al contenedor, o directamente una que nos permita llegar a una de las bases de datos, tanto la pública como la privada. Podemos elegir cualquiera de las opciones.

Nosotros vamos a elegir la primera de las opciones para el ejemplo: let contenedor = CKContainer.defaultContainer(). Obviamente, lo más interesante es hacerla propiedad de la clase para que así podamos usarla en toda ella.

Ahora vamos a suponer que queremos recuperar todos los datos de ejemplo que hemos guardado. Para ello tenemos que familiarizarnos con el concepto de los predicados: condiciones de acotación de consulta creados bajo un objeto NSPredicate. Estos predicados son como un if, aunque la mejor forma de entenderlos es como si tuviéramos un array de datos y al hacer un for in que los enumerara, usáramos un where donde decimos qué datos de dicho array queremos que se recuperen. Esa es la idea principal.

Para el primer caso, como queremos recuperar todo, vamos a usar una condición vacía de valor true. De esta forma, recuperará toda la información.

Es fácil de entender: primero creamos un objeto database con la base de datos pública (la propiedad publicCloudDatabase del contenedor), luego creamos un predicado cuyo valor sea true lo cual es una condición siempre verdadera para todo y por último generamos un objeto de tipo CKQuery que es nuestra consulta al Record Type que hemos creado en iCloud. Le decimos que recupere "Agenda" y le pasamos nuestro predicado a true.

Tras esto, hemos de entender que la consulta que ahora tenemos en query es asíncrona, es decir, no va a tener una respuesta inmediata si no que, dependiendo de nuestra conexión a internet, dispositivo y demás circunstancias (incluso de la propia carga del servicio) puede tardar unos segundos en contestar y devolver el resultado. Por lo tanto no es un proceso donde tengamos el dato de manera automática y eso ha de ser contemplado en el flujo de nuestra app. En el caso que los datos que recuperemos sirvan para inicializarla (como puede ser una app para tvOS) tendremos que crear una pantalla de inicio con algún mensaje de “Inicializando, espere…” o algo así.

Pero de hecho, tenemos la consulta preparada, pero aun no la hemos lanzado. Para eso tenemos que llamar a la instancia de la base de datos pública que hemos usado.

Ahora es cuando lanzamos la consulta: el primer parámetro con la consulta y en el segundo no pasamos identificación de zona porque vamos a usar la zona por defecto (la Default Zone que hemos visto en el Dashboard de CloudKit) y luego pasamos usando un trailing closure, el closure correspondiente al manejador o handler de la llamada. Porque este código en el closure será aquel que se ejecute cuando la consulte se haga efectiva y el dato se devuelva.

Preguntamos si error no es nil, ya que si no lo es, significaría que ha habido algún error y por lo tanto no haríamos nada. En caso que todo haya ido bien, hacemos un guard que nos enlace el opcional del parámetro de entrada del closure results, que es donde se devuelve el resultado de nuestra consulta. Si el enlace no se hace bien, salimos de la función directamente con un return pero si todo ha ido bien, hacemos una enumeración para recuperar los datos.

En este caso, resultado que es nuestra opcional result ya enlazada y convertida a no opcional es de tipo CKRecord tipo nativo que tendrá la colección de los datos y que es una especie de diccionario, donde podemos acceder a cada clave con su método .objectForKey.

El problema es que todos los valores que contiene están conformados al protocolo CKRecordValue que engloba todos los posibles tipos que puedan almacenar un CKRecord. Por lo tanto, tenemos que hacer un downcast hacia el tipo que nos interesa, en este caso forzado si queremos, pues ya sabemos sin lugar a dudas cuál es el tipo de cada dato (el que definimos en el dashboard de CloudKit).

Veremos que tarda un poco, pero enseguida obtendremos los datos que hemos guardado. ¿Y cómo obtendríamos datos más concretos? Puedes imaginemos que queremos una consulta que recupere solo los apellidos iguales a "Jobs". Solo habría que crear un predicado con esta condición en cualquiera de las siguientes formas:

Podemos usar una sintaxis o la otra, usando el parámetro abreviado %@ que hace de hueco o placeholder de un objeto que luego indicamos (en este caso la cadena "Jobs") o poner la cadena a buscar directamente (eso sí, entre comillas simples, no dobles). Y ojo: el operador de igualación es un solo =, no dos.

Otra cosa que podemos hacer es obtener la consulta ordenada, de origen, usando los índices de ordenación. Para ello tendríamos que haber creado, antes del NSPredicate, un objeto NSSortDescriptor en la siguiente forma:

En este caso, estamos diciendo que queremos que la consulta sea devuelta ordenada por el campo Apellidos en forma descendente (por eso ascending es false, si queremos que sea ascendente, obviamente hay que indicar true).

Luego, antes de hacer el performQuery asignamos la propiedad sortDescriptors de la propia consulta con un array donde ponemos este objeto NSSortDescriptor.

Que sea un array tiene una razón lógica: podemos crear más de un objeto de este tipo y conseguir ordenaciones por múltiples valores.

Grabando un valor

La grabación también es un proceso asíncrono, que lanzamos y se ejecuta en segundo plano, pudiendo controlar su resultado con un manejador o handler cuando se realice. En este caso, es tan simple como crear un objeto del tipo CKRecord diciéndole cómo se llama el tipo que queremos crear (que debe coincidir con el de iCloud, lógicamente) para luego añadir objetos y llamar al método saveRecord de la base de datos donde queramos grabarlo (la pública en este caso) pasándole el objeto CKRecord.

Creamos el objeto CKRecord de tipo "Agenda", usamos el método setObject de dicho objeto creado para dar un valor a cada clave del mismo (coincidiendo con la definición que conocemos) y luego ejecutamos el método saveRecord del objeto de la base de datos, pasándole el objeto CKRecord creado. Por último, pasamos un closure que sirva de handler de la operación: uno con dos parámetros, record y error donde podremos verificar si todo ha ido bien.

Costes

Ya hemos visto cómo funciona CloudKit a nivel básico, pero, ¿cuál es su coste? Pues, en principio, como hemos dicho, tenemos que tener una app enorme y productiva para empezar a pagar. Porque cuantos más usuarios tengamos, más capacidad gratuita tendrá CloudKit para nosotros.

Al principio, cuando creamos nuestra app y tenemos 0 usuarios activos, disponemos de 10GB de almacenamiento de recursos, 100MB de base de datos, 2GB de transferencia de datos y 40 solicitudes por segundo. Pero, podemos llegar a tener un máximo de 10 millones de usuarios activos. Por ejemplo, con 4 millones de usuarios alcanzaríamos el máximo de capacidad sin coste que consta de un Petabyte de capacidad para recursos (1.024 Terabytes), 10TB de almacenamiento en base de datos, 200TB de transferencia para la app y hasta 400 solicitudes por segundo.

A partir de ahí, podemos llegar hasta los 10 millones de usuarios activos, teniendo en cuenta el reparto de recursos, o decidir pagar para obtener mayor capacidad para nuestros usuarios, que si son 4 millones querrá decir que estamos con una app en las manos que podría ser monetizada fácilmente de diferentes formas.

Persistencia en la nube

Y esto es CloudKit, a nivel básico. Así podemos conseguir persistencia en la nube de una forma muy sencilla. Lógicamente, seguiremos hablando de él en próximas guías (por eso el I en número romano) pero, con esta introducción, tenemos una buena base para empezar a explorar por nuestra cuenta y trabajar con ella.

Os invitamos a que sigáis probando, que compartáis el artículo si os ha gustado y sido útil, y no dudéis en hacernos consultas o peticiones. Hasta entonces, Good Apple Coding.

Acerca de Julio César Fernández

Analista, consultor y periodista tecnológico, desarrollador, empresario, productor audiovisual, actor de doblaje e ingeniero de vídeo y audio.

Otras recomendaciones

UISearchBar

Lecciones por prototipos (III): barras de búsqueda (UISearchBar)

Descubre lo simple que es crear una barra de búsqueda para una vista de tabla, siguiendo paso a paso el proceso mediante un Playground interactivo. Veremos cómo implementarla, controlarla y reaccionar en tiempo real a sus resultados. Algo que no debe faltar en ninguna tabla o elemento que muestre muchos datos, para filtrarlos adecuadamente.

  • Javier Pérez

    Solo se puede utilizar CloudKit??? Pensaba q se podía utilizar core data también. CloudKit sigue siendo un ente raro para mí… Puede llegar a funcionar como una base de daros relacional??? Y si estás sin conexiona internet, sigue funcionando la aplicación??? Gracias!!

    • Julio César Fernández

      Hola Javier,
      Puede usarse Core Data también, si haces que el almacenamiento de los ficheros del mismo tenga persistencia en iCloud como documentos. Pero eso lo veremos más adelante.
      CloudKit funciona como una base de datos relacional, ya que a través de los campos de tipo Reference, se crean relaciones entre Record Types igual que en una base de datos relacional. Pero lo veremos más adelante (aunque nada te impide probar porque es muy sencillo).
      Si no tienes conexión a internet la app sigue funcionando sin problema, pero las grabaciones no se realizan. Para ello tendrías que tener un proceso local de almacenamiento y luego propagarlo a la nube o usar Core Data que se sincroniza cuando hay conexión.
      Un saludo

    • Hola Javier,
      Puede usarse Core Data también, si haces que el almacenamiento de los ficheros del mismo tenga persistencia en iCloud como documentos. Pero eso lo veremos más adelante.
      CloudKit funciona como una base de datos relacional, ya que a través de los campos de tipo Reference, se crean relaciones entre Record Types igual que en una base de datos relacional. Pero lo veremos más adelante (aunque nada te impide probar porque es muy sencillo).
      Si no tienes conexión a internet la app sigue funcionando sin problema, pero las grabaciones no se realizan. Para ello tendrías que tener un proceso local de almacenamiento y luego propagarlo a la nube o usar Core Data que se sincroniza cuando hay conexión.
      Un saludo

  • Enrique Condo

    Buen artículo, esperando ya las próximas entregas!

  • Orasbae

    Como siempre un gran placer leer tus artículos.
    Super bien explicado.

    Un gran saludo!

  • .[Roberto Hevens]

    Tengo una consulta.
    ¿El espacio en CLOUDKIT esta identificado por APP, APPLE ID? o ¿APP, DISPOSITIVO?

    Muchas gracias Julio.

    • El espacio en CloudKit es por app y depende del número de usuarios de la misma. Cuantos más usuarios activos tengas, mayor será este así como los recursos para poder usar. Los usuarios activos depende de los Apple ID que accedan.

      Un saludo,

      • .[Roberto Hevens]

        Muchas gracias Julio!
        Feliz 2016!

  • .[Roberto Hevens]

    Hola Julio:

    Escuche tu podcast 2×8 felicitaciones.

    Voy a crear una App para iPhone y utilizare CLOUDKIT. Sería factible que nos dejes más información sobre cloudkit (II).

    Muchas Gracias.