Frameworks de terceros

Ya hemos visto que no hay un API en Swift (ni en Objective-C) para usar SQLite y que tampoco es de esperar que aparezca ninguno, dado el énfasis que Apple hace en Core Data para las tareas de persistencia. No obstante esto no ha impedido que aparezcan librerías de terceros implementando un API para SQLite que sea de más alto nivel y más sencillo de usar en iOS.

Hay multitud de wrappers de SQLite en Swift. De ellos uno de los más conocidos es SQLite.swift.

No obstante los frameworks más usados para SQLite siguen siendo Objective-C, el más conocido es probablemente FMDB. Vamos a ver una introducción muy básica a este framework

Uso de FMDB en proyectos Swift

Swift es una librería hecha en Objective-C pero ya sabemos que podemos mezclar Objective-C con Swift en un proyecto.

Instalación de la librería

Aunque la forma recomendada para instalar FMDB en un proyecto es a través del uso de CocoaPods probablemente es más simple importar directamente los fuentes. Para eso nos podemos bajar directamente el .zip de FMDB de su repositorio de Github.

Una vez descomprimido el .zip vamos al directorio fmdb-master/src/fmdb e importamos todos los archivos de este directorio a nuestro proyecto (menos el .plist, que no es necesario). Al importarlos, Xcode detectará que son archivos en Objective-C y nos preguntará si queremos crear el "bridging header". Le decimos que sí y se creará el fichero vacío <nombre-del-proyecto>-Bridging-Header.h, en el que añadiremos la siguiente línea:

#import "FMDB.h"

A partir de este momento ya podemos usar las clases de FMDB dentro de nuestro código Swift

El API de FMDB

Vamos a ver unos cuantos ejemplos de uso de la librería FMDB para que se vea que es bastante más fácil de usar que el API C. La librería tiene varias clases básicas (podéis consultar online la referencia del API). De ellas las dos más usadas en un código típico son:

  • FMDatabase, que es un objeto que representa a la base de datos. Es la clase más importante y con la que se va a interactuar la mayor parte del tiempo. Se usa para abrir/cerrar la conexión y también para ejecutar las consultas.

  • FMResultSet, que encapsula un conjunto de registros resultado de una consulta de selección. Podemos ir iterando por el conjunto y obtener los campos del registro actual, al igual que con el API C, pero de forma mucho más sencilla: por ejemplo podemos obtener el valor de un campo por nombre, y las llamadas están bastante simplificadas para adaptarse mejor a los casos de uso más típicos.

Por ejemplo, veamos cómo se abriría una base de datos, se ejecutaría una consulta de selección y se iría iterando por el conjunto de registros resultante

let dbPath = Bundle.main.path(forResource: "prueba", ofType: "db")
if let db = FMDatabase(path: dbPath) {
    if (!db.open()) {
        print("Error al abrir la bd: \(db.lastErrorMessage())")
    }
    else {
        if let resultSet = try? db.executeQuery("SELECT * FROM Personas", values: nil) {
            while (resultSet.next()) {
               print(resultSet.string(forColumn: "nombre"))
            }
        }
    }
}
  • Para inicializar la BD necesitamos el path y usamos el método de clase databaseWithPath

  • Abrir y cerrar la BD es tan sencillo como llamar a open y close. El método open devuelve un valor booleano que indica si se ha abierto o no correctamente. En caso de error en una operación de BD podemos acceder a más información sobre el mismo con los métodos lastErrorMessage o lastErrorCode.

Cuando se abre una BD que no existe, FMDB la crea automáticamente, se puede cambiar este comportamiento llamando a openWithFlags con los parámetros adecuados, en lugar de a open. También se puede usar una BD en memoria si se le pasa NULL como path a databaseWithPath

  • Las consultas se ejecutan con executeQuery y devuelven una instancia de la clase FMResultSet. Para iterar por el resultset usamos el método next.

  • Podemos obtener los campos del registro actual con una serie de métodos XXXForColumn:, donde la parte inicial del nombre es el tipo de datos de la columna (string, int, long, date,…). Obsérvese además que se accede a la columna por nombre y no por posición (aunque también se puede acceder por posición con XXXForColumnIndex:).

Por supuesto podemos ejecutar consultas con parámetros. El método executeQuery: admite una lista de argumentos con los valores, por ejemplo:

let rs = db.executeQuery("SELECT * FROM alumnos
                       where fecha_nacimiento<?", values:[fecha_ref];

Para ejecutar una consulta de actualización se usa executeUpdate: en lugar de executeQuery.

También podemos usar de forma sencilla parámetros por nombre (algo que en el API C es un poco tedioso). Basta con crear un diccionario con los nombres de los parámetros como claves y los valores deseados

let query = "INSERT INTO personas (dni, nombre) VALUES (:dni, :nombre)"
let datos = [
    "dni" : "1222333K",
    "nombre": "Pepito"
]
db.executeUpdate(query, withParameterDictionary: datos)

Last updated