Si queremos desarrollar una aplicación que permita compartir y sincronizar datos o ficheros entre diferentes dispositivos, necesitaremos utilizar un servicio de backend que nos permita realizar estas tareas. En el caso de dispositivos con iOS, o macOS, podemos utilizar CloudKit. En este artículo vamos a ver cómo se desarolla una aplicación de tareas con CloudKit.

¿Qué es CloudKit?

CloudKit es el servicio de almacenamiento de Apple que permite que sus aplicaciones guarden datos y ficheros de forma remota. Apple presentó CloudKit en la WWDC 2014 como una nueva librería que permitía comunicar con los servidores iCloud.

Con CloudKit y Swift podemos almacenar cualquier tipo de datos y ficheros de forma gratuita hasta 10 GB de almacenamiento de ficheros, 100 MB de almacenamiento de base de datos, 2 GB de transferencia de datos y 40 peticions por segundo. Pero podemos alcanzar una capacidad mucho mayor de forma gratuita en función del número de usuarios que se agreguen (1 PB de almacenamiento de fichero, 10 TB de base de datos, 200 TB de transferencia de datos y 400 peticiones por segundo).

Capacidades de uso de CloudKit.

Podemos comparar CloudKit con otras soluciones de tipo BaaS (Backend as a Service), como Firebase.

Con CloudKit, Apple también ofrece:

  • Una API para comunicar y transferir datos entre los dispositivos e iCloud.
  • Un escritorio para administrar los datos almacenados en los servidores de Apple, medir la actividad de los usuarios o el consumo de ancho de banda.

Uso de CloudKit

Para usar CloudKit hemos de tener en cunta que:

  • Hay que esta registrado en el iOS Developer Program para poder usar CloudKit.
  • Como los datos no se guardan de forma local, sino en los servidores de Apple, una aplicación que use CloudKit no es útil sin conexión a Internet.
  • Los datos de los usuarios están protegidos, ya quue los desarrolladores solo pueden acceder a sus propias bases de datos y no lo datos privados de los usuarios.
  • Apple recomienda notificar al usuario si se produce algún error (como que halla un error en el proceso de guardado), para que sepa que puede haber perdido datos o que estos no se han guardado.

Bases de datos de CloudKit

Para empezar a trabajar con CloudKit hemos de tener en cuenta que, de la misma forma que cada aplicación tiene su propio sandbox, de forma similar cada aplicación tiene un contenedor en iCloud (si hemos registrado la aplicación para ello, tal como veremos más adelante). En CloudKit se definene tres tipos de bases de datos:

  • Privada
  • Pública
  • Compartida

Base de datos privada

Cada usuario de una aplicación (si esta ha sido registrada para usar iCloud) tiene una base de datos privada en un contenedor de iCloud, siempre que el usuario haya iniciado sesión en su cuenta de iCloud. Al ser privada, los desarrolladores no pueden acceder a los datos almacenados en esta base de datos.

Base de datos pública

Un contenedor de iCloud también contiene una base de datos pública, pero en este caso sus datos pueden ser leídos por todos los usuarios de la aplicación, aunque no hayan iniciado sesión con su cuenta de iCloud. Hay que tener en cuenta que como los registros de CloudKit siempre tienen un propietario, solo los usuarios que hayan iniciado sesión en iCloud podrán escribir datos en la base de datos pública.

Base de datos compartida

De la misma forma que en la base de datos privada, la base de datos compartida solo es accesible si se ha iniciado sesión en iCloud. Se utiliza para compartir registros de la base de datos privada de un usuario con otros usarios de la aplicación.

Conceptos a tener en cuenta

Al trabajar con CloudKit hemos de tener en cuenta algunos conceptos:

  • Contenedor. Se trata de instancias de la clase CKContainer que, como hemos visto, guardan los datos del usuario. Podemos acceder al contenedor por defecto de la aplicación (default) o a los que hayamos creado:
  • Base de datos. Como hemos visto, en CloudKit podemos encontrar tres bases de datos: pública, privada y compartida. Se representan mediante objetos del tipo CKDatabase.
  • Registro. Son objetos del tipo CKRecord, y podemos considerarlos como diccionarios en los que las claves (keys) son los campos de las tablas en la base de datos.
  • Zona. Se representan mediante objectos del tipo CKZone, y es lugar en el que se guardan los datos. En CloudKit, todas las bases de datos tienen una zona por defecto (Default Zone), pero también podemos crear zonas personalizadas, aunque solo en las bases de datos privadas.

Proyecto TodoList

Vamos a crear una aplicación de lista de tareas, TodoList, que se guardarán en iCloud. Este proyecto lo puedes descargar de GitHub.

En primer lugar creamos un nuevo proyecto en Xcode con la plantilla Single View App.

Creación del proyecto TodoList en Xcode 11.

A continuación, introducimos los datos del proyecto y seleccionamos las opciones Use Core Data y Use CloudKit.

Selección de las opciones Use Core Data y Use CloudKit.

Ahora debemos añadir la capacidad al proyecto de usar iCloud. Para ello seleccionamos la aplicación en Targets, después la pestaña Signing & Capabilities y, finalmente, la opción +Capability. Aparecerá un menú en el que seleccionaremos la opción iCloud.

Seleccionamos la capacidad de iCloud en el proyecto.

Ahora, en la pestaña Signing & Capabilities tenemos la opción iCloud, en la que activaremos la opción CloudKit y añadiremos un contenedor.

Adición de un contenedor.

Escritorio de CloudKit

Ahora que hemos creado el proyecto y el container, hemos de crear los registros para los datos que usaremos en la aplicación. Esto podemos hacerlo desde el escritorio de CloudKit, lo que nos permitirá conocer cómo funciona. Para ello hacemos click en el botón CloudKit Dashboard.

Escritorio de CloudKit.

Tal como se observa en la imagen, el escritorio de CloudKit presenta seis secciones:

  • API Access. Gestiona los tokens de la API y las claves servidor a servidor que permiten las llamadas al servicio web.
  • Data. Gestiona los registros y sus tipos, índices, subscripciones y seguridad en las bases de datos públicas, privadas y compartidas.
  • Schema. En esta sección encontramos las opciones de Tipos de registro, Índices, Roles de seguridad y Tipos de subscripción.
  • Logs. Muestra los datos tanto históricos como en tiempo real de la actividad en el servidor o de las notificaciones.
  • Telemetry. Muestra gráficas del uso y rendimiento del lado de servidor en el uso de las bases de datos, así como de los eventos de notificación.
  • Usage. Muestra información sobre los usuarios activios, las peticiones por segundo o el almacenamiento de datos.

Creación de la base de datos desde el escritorio de CloudKit

Tal como hemos comentado anteriormente, vamos a crear una aplicación sencilla que nos permitirá gestionat una lista de tareas, que sincronizaremos en iCloud. Como se trata de un ejemplo sencillo, crearemos una base de datos con una tabla para guardar las tareas, con las siguiente propiedades: título, fecha de creación, fecha de modificación y si esta realizada o no.

Desde el Escritorio de CloudKit, seleccionamos Schema > Record Types > New Type, y le damos el nombre de Task.

Adición del Tipo de Registro Task.

Una vez creado este tipo de registro lo seleccionamos y empezamos a añadir campos (después de añadirlos hacemos clic en Save):

  • title (de tipo String)
  • createdAt (de tipo Date)
  • modifiedAt (de tipo Date)
  • checked (de tipo Int64, ya que no existen booleanos)
Creación de los diferentes campos de Task.

Los datos pueden tener alguno de los siguientes tipos básicos (también se pueden hacer listas o arrays, List, de estos datos):

  • Asset. Se trata de un fichero asociado a un registro, aunque almacenado independientemente (tiene un límite de 1 MB).
  • Date/Time. Un dato de fecha y hora.
  • Int(64). Es un número entero de 64 bits.
  • Double. Es un número doble.
  • Bytes. Se trata de un buffer de bytes que se almacena en el propio registro.
  • String. Se trata de una cadena de texto.
  • Location. Es para datos geográficos de localización.
  • Reference. Es una referencia a otra tabla para poder crear relaciones entre ellas.

Finalmente, seleccionamos Edit Indexes y añadimos dos:

  • createdAt de tipo SORTABLE (lo que nos permitirá ordenar los resultados por fecha de creación).
  • checked de tipo QUERYABLE (para buscar si las tareas están marcadas o no como completas).
  • recordName de tipo QUERYABLE (para recuperar los registros).

De esta forma obtenemos la siguiente imagen:

Aspecto final del esquema.

Adición de datos de muestra

Una vez creada la tabla Task, podemos añadir algunos datos de muestra desde el Escritorio de CloudKit. Para ello, desde el menú desplegable seleccionamos la opción Data.

Selección de la opción Data.

Seguidamente, en el panel de la izquierda seleccionamos en el menú Database seleccionamos Public Database y, en el menú Zone, seleccionamos _defaultZone (es la que contiene los registros públicos de la aplicación). Luego, en en menú Type seleccionamos Task (nombre de la tabla) y luego hacemos clic en el botón New Record para añadir datos.

Adición de registros a la base de datos.
Adición de los datos.

Un poco de Swift

Una vez hemos introducido un par de registros de ejemplo, vamos a empezar a desarrollar una aplicación sencilla. Este proyecto lo puedes encontrar completo en GitHub.

Diseño de la interfaz

Este proyecto constará básicamente de un componente UITableView, que será el que muestre las tareas. Cada celda de esta tabla será una tarea de la que se mostrará el título, la fecha de creación y un ícono para indicar si se ha realizado o no.

Esta tabla estará dentro de un componente UINavigationController en la barra del cual pondremos el título y un botón de añadir tareas. En este proyecto, todo esto se hará mediante código, sin utilizar storyboards o ficheros .xib.

Diseño de la interfaz.

Creación del proyecto

Para trabajar sin storyboards al establecer un proyecto en Xcode 11 hemos de hacer unos pasos tras haberlo creado:

  • Eliminamos el fichero Main.storyboard.
  • En la pestaña General (TARGETS), vamos al selector Main interface y eliminamos Main, dejando el campo en blanco.
Hay que borrar Main y dejar el campo en blanco.
  • Finalmente, en la pestaña Info (TARGETS) vamos a Application Scene Manifest > Scene Configuration > Application Session Role > Item 0 (Default Configuration) y eliminamos el campo Storyboard Name.
Borrado del campo Storyboard Name en el fichero Info.plist.

Como ahora ya no llamaremos al Main.storyboard para iniciar el proyecto, vamos al fichero SceneDelegate.swift, y en la función scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) y sustituimos su contenido por el siguiente código:

Este código crea un UINavigationController y le añade el controlaldor ViewController. Después lo asigna como rootViewController.

Creación del gestor de registros

A continuación creamos un gestor que realizará las tareas relacionadas con CloudKit (recuperar registros, guardarlos…). La vamos a denominar CloudKitManager.swift. En primer lugar, añadiremos la funcionalidad de recuperar los registros de iCloud.

En fetchTasks(completion: @escaping ([CKRecord]?, FetchError) -> Void), lo primero que hacemos es recuperar el contenedor (tipo CKContainer) de nuestra aplicación a partir del identificador que le hemos dado (iCloud.com…) y luego obtenemos la base de datos pública, que es en la que hemos creado anteriormente los registros de demostración.

Luego creamos un búsqueda (query, de tipo CKQuery) en la que indicamos el nombre de la tabla que hemos creado en iCloud (Tasks), y se añadimos un criterio de ordenación para los registros que se obtengan: en este caso, ordenará los registros según la fecha de creación (createdAt) de más nuevo a más antiguo (ascending: false).

A continuación, ejecutamos la query en la base de datos pública según el método publicDatabase.perform(<query: CKQuery, inZoneWith: CKRecordZone.ID?, completionHandler: ([CKRecord]?, Error?) -> Void>), en el que introducimos la query que hemos creado, el identificador de zona (que es el de la zona por defecto, default, y que es la que hemos seleccionado al crear la tabla en iCloud).

Al ejecutar la query obtenemos como respuesta un par de parámetros: un lista de registros [CKRecord] y un error (ambos casos son condicionales, ya que pueden tener el valor nil).

Esta respuesta la procesamos en el método processQueryResponseWith, en el cual comprobamos si el ha se ha producido algún error al realizar la query, si se han devuelto algún registro y cuáles son estos registros. Para facilitar el tratamiento de los errores, se ha creado enum FetchError, que recoge los posibles errores.

Creación del componente UITableView

El componente UITableView será sencillo, lo único que personalizaremos es que las celdas puedan tener una altura variable, para mostrar títulos de tareas de hasta tres líneas (TasksTableViewController.swift):

También creamos una celda personalizada (TaskCell.swift):

En la que el método toggleChecked() permite cambiar, por ahora, el estado del ícono de tarea realizada.

Ahora en el la clase ViewController.swift ya podemos mostrar la tabla y ejecutar la llamada iCloud.

Borrado de registros

Para borrar registros habilitaremos el uso del arrastrado de las celdas para que aparezca la opción de borrar. Esto lo conseguiremos añadiendo el siguiente método a la clase TasksTableViewController:

También modificaremos esta clase para inyectar una intancia de CloudKitManager.

Y en la clase ViewController cambiamos la formar de instancias la clase TasksTableViewController, ya que ahora inyectaremos la instancia de CloudKitManager:

En CloudKitManager añadimos estos métodos para el borrado de registros y añadimos el caso deletingError al enum FetchError:

Y en la clase TasksTableViewController modificamos el último método:

Ahora ya podemos probar el borrado de tareas.

Borrado de un registro.

Adición de una tarea

Para poder crear una tarea, crearemos un UIViewController al que navegaremos haciendo clic en un botón ‘+’ de la barra de navegación. Este nuevo UIViewController, al que denominaremos AddTaskController, estará compuesto simplemente por un componente UITextField, en el que introduciremos el texto de la tarea, y un componente UIButton, para adicionarlo.

En este código hay varios puntos importante:

  • En el método init se inyecta una instancia de CloudKitManager, para poder gestionar la adición de un registro.
  • El método asociado al boton ‘Add task‘ se llama al método addTask del CloudKitManager, que gestionará la adición de nuevos registros. El código que se añade para este caso en CloudKitManager es (también se ha añadido un nuevo caso, addingError, en el enum FetchError):
  • Finalmente, se ha creado un protocolo para pasar el registro creado al ViewController. Finalmente, se navega al controlador de partida (ViewController).

Para poder llamar a la clase AddTaskController añadimos un botón a la barra de navegación. Esto lo hacemos mediante el componente UIBarButtonItem, al que le asociamos el método addTask. En este método instanciamos el controllador, definimos el delegate y hacemos push al nuevo controlador.

Como hemos definido el delegate para ViewController, tenemos que hacer que ViewController adopte los métodos de este protocolo. Esto lo hacemos mediante una extension de ViewController (para organizar el código):

En este implementación del método addedTask se gestionaría cualquier posible error y, en el caso de que todo sea correcto, se manda el registro creado a un nuevo método add(task: CKRecord) que crearemos en la clase TasksTableViewController:

Este método lo que hace es añadir el nuevo registro al principio de la lista de registros (es el más nuevo) y luego recarga los datos de la tabla.

Adición de un nuevo registro.

Actualizar una tarea

Ya hemos visto como recuperar, borrar u crear registros con CloudKit. Ahora veremos como actualizar un registron en CloudKit. Para ello utilizaremos un caso simple, el que corresponde a completar una tarea y marcarla como completada.

En primer lugar añadimos un nuevo método en CloudKitManager:

Al pasar un objeto que ya existe y guardarlo en CloudKit, sus valores se actualizan. Para llamar a este método cuando marcamos la tarea como completa, creamos un protocolo que permita pasar el registro modificado desde la celda a la clase TasksTableViewController:

Ahora en la clase TasksTableViewController añadimos el delegate a la celda:

Y luego hacemos que la clase TasksTableViewController adopte el protocolo:

Si marcamos un registro como completado y luego apagamos la aplicación y la volvemos a cargar, vemos que el registro ha sido modificado en iCloud.

Modificación de un registro.

Conclusión

Hemos visto cómo podemos crear una aplicación que utilice CloudKit para sincronizar datos con iCloud. En esta apliación hemos integrado los métodos básicos CRUD (Create, Read, Update, Delete) de una base de datos. Recuerda que puedes descargar el proyecto completo de GitHub.


0 comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Sígueme en Feedly
shares