Guía de la base de datos Realm – Construyendo una app de notas en Swift para iOS

Si prefieres los vídeos a los artículos escritos, este artículo es una versión escrita de un vídeo que he producido. El contenido es idéntico.

Este vídeo es el primero de una serie que espero que sirva para enseñarte a utilizar frameworks y herramientas de iOS como Siri Shortcuts, CloudKit, etc. Si tienes un framework o característica específica que te gustaría que cubriera esta serie, no dudes en escribirme a jordanosterbergshadowsystemstech, o en Twitter @josterbe1.

Tabla de contenidos

  1. Introducción
  2. Instalación de dependencias
  3. El modelo
  4. Implementación de Realm
  5. Conclusión

Sin más preámbulos, echemos un vistazo a la aplicación que vamos a construir en esta serie…

Introducción

La lista de notas de la aplicación
Edición de una nota individualmente

Tenemos nuestra lista de notas, y cuando tocamos una, podemos leerla y empezar a editarla, o borrarla. Bastante sencillo. Notarás si descargas el proyecto de inicio que las notas no persisten cuando entras y sales del NoteDetailController al tocar la nota «Visita al Apple Park». Es decir, el contenido de la nota no se guarda cuando la editas.

Eso es lo que vamos a construir en este artículo, utilizando la base de datos Realm. He usado Realm durante unos dos años, y encuentro que su simplicidad compensa el costo de usar una biblioteca de terceros. De hecho, lo uso en la aplicación en la que paso la mayor parte de mi tiempo de desarrollo personal (ver https://countdowns.download) tanto en macOS como en iOS.

Instalando dependencias

Para empezar a usar Realm, necesitamos instalarlo usando CocoaPods. CocoaPods, por si no lo sabes, es una herramienta de gestión de dependencias muy utilizada en el espacio iOS. Si no tienes CocoaPods instalado en tu Mac, puedes usar el comando sudo gem install cocoapods para empezar. Ahora, abra la carpeta del proyecto, así como una ventana de Terminal dentro de ese directorio.

Escriba pod init, y luego open Podfile.

Dentro del «Podfile» recién creado, necesitamos escribir algo de texto que informará a CocoaPods qué bibliotecas le gustaría instalar en su proyecto.

Debajo de # Pods for NotesApp, escriba estas dos líneas:

pod 'Realm', '~> 3.12.0'pod 'RealmSwift', '~> 3.12.0'

Su Podfile debería tener un aspecto similar a este después de haber escrito esas líneas:

platform :ios, '12.0'target 'NotesApp' do use_frameworks! # Pods for NotesApp pod 'Realm', '~> 3.12.0' pod 'RealmSwift', '~> 3.12.0' target 'NotesAppTests' do inherit! :search_paths end target 'NotesAppUITests' do inherit! :search_paths endend

Ahora que hemos añadido nuestras dependencias, vamos a pedirle a CocoaPods que las instale con pod install

Esto llevará algún tiempo cuando uses CocoaPods por primera vez. No te preocupes, CocoaPods sólo está descargando algunos componentes iniciales. Esto no ocurrirá cada vez que pod install.

Tu ventana de Terminal se verá así una vez que ese comando termine de ejecutarse:

Ventana de Terminal

Después de esto, si ya has abierto el NotesApp.xcodeproj, cierra fuera de él. Cuando utilice CocoaPods, debe utilizar el archivo .xcworkspace en lugar del archivo predeterminado .xcodeproj. Abra el archivo NotesApp.xcworkspace y diríjase a Note.swift.

El Modelo

Esta clase contiene nuestro objeto modelo Note, que contiene unas cuantas propiedades básicas:

class Note { var identifier: String var content: String var lastEdited: Date init( identifier: String = UUID().uuidString, content: String, lastEdited: Date = Date()) { self.identifier = identifier self.content = content self.lastEdited = lastEdited }}

Código estándar del modelo, no pasa nada especial aquí.

También tenemos una extensión de nuestro objeto Note en el mismo archivo, que subclasifica como protocolo llamado Writeable

extension Note: Writable { func write(dataSource: DataSource) { self.lastEdited = Date() dataSource.store(object: self) } func delete(dataSource: DataSource) { dataSource.delete(object: self) }}

Dentro de las funciones write y delete, verás que tenemos una propiedad DataSource. DataSource es un protocolo genérico para ayudar a que la modificación de datos sea lo más abstracta posible en los niveles superiores de nuestro código.

Aquí está la definición del protocolo:

protocol DataSource { func store<T>(object: T) func delete<T>(object: T)}

Si no estás familiarizado con los genéricos, cada una de nuestras funciones tiene un parámetro T que esencialmente significa que puede ser cualquier tipo de objeto. Esto no se utiliza mucho en nuestro proyecto, pero se podría evolucionar y utilizar más para crear múltiples DataSources con diferentes restricciones en torno a los objetos que pueden almacenar.

Implementamos nuestro protocolo DataSource en NoteDataSource. No hay nada especial aquí tampoco, aparte de un pequeño detalle que me gustaría señalar para la explicación.

Cada vez que store o delete objetos, utilizamos la siguiente llamada a NotificationCenter:

NotificationCenter.default.post(name: .noteDataChanged, object: nil)// We also have this extension of Notification.Name to make sending and receiving this notification simple.extension Notification.Name { static let noteDataChanged = Notification.Name(rawValue: "noteDataChanged")}

Esencialmente, cada vez que cualquier nota se almacena o se elimina, informamos a cualquier oyente que nuestros datos han cambiado, para que puedan actualizar su UI en consecuencia.¡

Con todos nuestros archivos de modelo y clases fuera del camino, vamos a empezar a implementar Realm!

Implementando Realm

Hay tres pasos para integrar Realm con nuestro proyecto:

  1. Crear el objeto Realm
  2. Crear un puente entre nuestro objeto Realm y nuestro objeto primitivo Note
  3. Empezar a recuperar y modificar datos con Realm

Crear el objeto Realm

Este paso es relativamente sencillo. Vamos a crear un nuevo archivo Swift llamado RealmNote.swift. Dentro de RealmNote.swift, importamos el framework RealmSwift y creamos una declaración de clase como la siguiente:

import RealmSwiftclass RealmNote: Object {}

Subclasificaremos la clase Object de Realm que nos permitirá utilizar RealmNote en las funciones de la base de datos de Realm.

Ahora, añadimos las tres propiedades que tenemos en nuestro modelo Note:

@objc dynamic var identifier: String = ""@objc dynamic var content: String = ""@objc dynamic var lastEdited: Date = Date()

Las piezas @objc dynamic de nuestra declaración de variables exponen nuestras propiedades a Objective-C, en el que están escritas muchas de las capas iOS de Realm. También permite a Realm escuchar los cambios en nuestros objetos RealmNote.

Para terminar nuestra clase, anula la class func primaryKey, que devuelve una cadena opcional (String?) con el valor «identificador». Esto informa a Realm de que la clave primaria de RealmNote, una forma de identificar de forma única nuestros objetos, se almacena en la propiedad «identificador».

Una vez que hayas completado estos pasos, tu RealmNote tendrá este aspecto:

class RealmNote: Object { @objc dynamic var identifier: String = "" @objc dynamic var content: String = "" @objc dynamic var lastEdited: Date = Date() override class func primaryKey() -> String? { return "identifier" }}

Eso es todo para el primer paso.

Planificación entre nuestro objeto Realm y nuestro objeto primitivo Note

Tenemos dos objetos modelo separados: Note y RealmNote. RealmNote se utiliza internamente cuando se trata de Realm, con el fin de mantener nuestra capa de modelo desacoplado de nuestra interfaz de usuario. Mediante el uso de dos objetos separados, podríamos dejar de usar Realm en el futuro si surge la necesidad.

En el archivo RealmNote.swift, crear una extensión de RealmNote:

extension RealmNote {}

Ahora, crear un convenience init dentro de la extensión que toma un Note como su única propiedad. Esto nos permitirá crear objetos RealmNote utilizando un objeto Note.

convenience init(note: Note) { self.init() self.identifier = note.identifier self.content = note.content self.lastEdited = note.lastEdited}

Genial, ahora, para acabar con RealmNote.swift, crea una variable Note dentro de la extensión que se inicialice a partir de un RealmNote:

var note: Note { return Note(realmNote: self)}

No te preocupes si Xcode te da un error sobre el inicializador de Note, estamos a punto de arreglarlo.

Dirígete a Note.swift, donde vamos a escribir la otra mitad de nuestro puente. Este código es esencialmente lo mismo que la extensión de RealmNote.

extension Note { convenience init(realmNote: RealmNote) { self.init(identifier: realmNote.identifier, content: realmNote.content, lastEdited: realmNote.lastEdited) } var realmNote: RealmNote { return RealmNote(note: self) }}

Genial, ese es el segundo paso. Ahora podemos acceder a un RealmNote desde un Note, y a un Note desde un RealmNote. Esto también significa que este código es perfectamente válido:

Note(content: "Example").realmNote.note.realmNote.note.realmNote.note.realmNote// and so on...

Bromas aparte, vamos a terminar nuestra aplicación con el paso tres.

Comienza a recuperar y modificar datos con Realm

Entra en NoteDataSource.swift, y import RealmSwift antes de la declaración de la clase. Antes de modificar, eliminar y recuperar objetos Realm, necesitamos una instancia de la base de datos Realm. Sustituye el init de NoteDataSource por esto:

var realm: Realminit() { // Load our data self.realm = try! Realm()}

Esto creará una instancia de la base de datos Realm. En producción, es probable que no quieras utilizar el operador bang (!) al crear la instancia porque tu aplicación se bloqueará si la base de datos no está disponible.

A continuación, vamos a editar la función store para almacenar realmente objetos en nuestra base de datos.

Escribir con Realm es sencillo:

try? self.realm.write {}

Dentro de ese bloque de código, podemos actualizar objetos dentro de la base de datos. Escribe esto:

self.realm.add(note.realmNote, update: true)

Esto creará un nuevo RealmNote en la base de datos o actualizará uno existente si lo hay. Ya está. Ahora estamos almacenando objetos en la base de datos Realm (la llamada a la función de escritura de la nota se realiza en NoteDetailController.swift si quieres ver la función que se utiliza realmente para realizar esta escritura.)

Ahora, vamos a escribir nuestra función delete. Debido a la forma en que escribimos nuestro puente (sin consultar a Realm cada vez que creamos un RealmNote a partir de un Note), debemos obtener el objeto directamente de la base de datos utilizando el identificador de la nota en lugar de utilizar su propiedad realmNote.

Esto es sencillo:

if let realmNote = self.realm.object(ofType: RealmNote.self, forPrimaryKey: note.identifier) {}

Esto le pide a Realm que recupere un objeto RealmNote de la base de datos, con el identificador, o clave primaria, de note.identifier.

Esto puede ser un poco raro. Por alguna razón, la aplicación se bloquea si utilizamos la función tradicional write en Realm. Como solución, este código es perfectamente válido y esencialmente realiza la misma tarea que write:

self.realm.beginWrite()self.realm.delete(realmNote)try? self.realm.commitWrite()

Empezamos nuestra escritura, borramos el objeto, y confirmamos nuestra escritura.

¡Con esto, hemos construido una aplicación de notas que funciona!

Conclusión

Espero que hayáis disfrutado de este tutorial sobre cómo utilizar la base de datos Realm. Me he divertido mucho haciéndolo y no puedo esperar a evolucionar esta serie y añadir más características a nuestra aplicación con el tiempo, como los atajos de Siri, CloudKit, y más. Gracias por leer.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.