Serialización
Serialización/Codificación de datos¶
Desde la versión 4 de Swift la serialización de datos en iOS se ha uniformizado en lo que se denomina encoding/decoding. El encoding es el proceso que nos permite pasar de una estructura de datos en memoria a otro formato más adecuado para archivar o transmitir la información, por ejemplo JSON, XML,... El decoding es el proceso inverso.
El protocolo Codable
¶
Para que un objeto sea "codificable/decodificable" debe implementar este protocolo. En la mayoría de casos no será necesario escribir nada de código, siempre que nuestra clase/struct esté compuesta por campos que sean conformes con Codable
. Muchos tipos básicos de Swift lo son, como los String
, Int
, Float
, Date
, Array
...
En realidad
Codable
no es más que una combinación de los protocolos,Encodable
(para codificar) yDecodable
(para decodificar):
Como ejemplo, supongamos que tenemos una estructura Alumno
que representa un alumno de un determinado curso o asignatura. Su definición podría ser algo como:
struct Alumno {
var nombre : String
var nota: Float
var fechaNacimiento: Date
}
Como los campos de la clase son conformes a Codable
para que la propia clase lo sea nos basta con declararla como tal:
struct Alumno : Codable {
//..el resto es exactamente igual
}
Ahora solo necesitamos un "encoder", una clase capaz de transformar algo Codable
en un formato determinado. En los APIs de iOS tenemos encoders para transformar a/desde formatos como JSON o XML. En la siguiente sección veremos un ejemplo.
Almacenar Codable
en archivos¶
Una posibilidad es transformar nuestros datos Codable
en JSON, XML o un formato para el que tengamos un encoder y luego almacenar el formato resultante en un archivo.
Veamos un ejemplo con JSON:
//Esto se usa solo para poder generar fechas a partir de cadenas,
//no está relacionado directamente con la serialización
let df = DateFormatter()
df.dateFormat = "dd-MM-yyyy"
//Definir un alumo cuyos datos guardaremos
let alumno = Alumno(nombre: "Pepe", nota: 10.0, fechaNacimiento: df.date(from: "10/10/2000")!)
//Convertir el alumno a JSON
let encoder = JSONEncoder()
let datos = try! encoder.encode(alumno)
//Almacenarlo en un archivo llamado "result.json" en la carpeta Documents
var urlDocs = FileManager.default.urls(for:.documentDirectory,
in:.userDomainMask)[0]
let urlFichero = urlDocs.appendingPathComponent("result.json")
try! datos.write(to: urlFichero)
En JSON hay algunos tipos de datos, como las fechas, que no están estandarizados. El JSONEncoder
soporta varios formatos, que podemos seleccionar a través de la propiedad dateEncodingStrategy
. Por ejemplo para guardar las fechas en formato ISO8601:
encoder.dateEncodingStrategy = .iso8601
Para leer los datos del archivo usaríamos un JSONDecoder
let datosLeidos = try Data(contentsOf: urlFichero)
let decoder = JSONDecoder()
let alumnoLeido = try decoder.decode(Jugador.self, from: datosLeidos)
Si al almacenar los datos hubiéramos cambiado el formato de fecha, tendríamos que cambiarlo en la propiedad dateDecodingStrategy
del decoder.
Otra opción en lugar de usar JSON o XML es usar las clases NSKeyedArchiver
y NSKeyedUnarchiver
que sirven para "archivar" y "desarchivar" Codable
s, respectivamente.
Para codificar los datos usamos el método encodeEncodable
de NSKeyedArchiver
//Alumno que luego guardaremos
let df = DateFormatter()
df.dateFormat = "dd-MM-yyyy"
let alumno1 = Alumno(nombre: "Pepe", nota: 10.0, fechaNacimiento: df.date(from: "10/10/2000")!)
//El archivo se llamará "datos.dat" dentro del directorio de documentos de la app
let urlDocs = FileManager.default.urls(for:.documentDirectory,
in:.userDomainMask)[0]
let urlArchivo = urlDocs.appendingPathComponent("datos.dat")
let archiver = NSKeyedArchiver()
do {
//codificamos
try archiver.encodeEncodable(alumno1, forKey: NSKeyedArchiveRootObjectKey)
//los datos codificados están en "encodedData". Los guardamos en el archivo
try archiver.encodedData.write(to: urlArchivo)
} catch {
print(error)
}
Para el paso contrario (decoding) usamos el método decodeTopLevelDecodable
de NSKeyedUnarchiver
:
//aquí "urlArchivo" tiene el mismo valor que en el ejemplo anterior
let datos = try Data(contentsOf: urlArchivo)
let unarchiver = NSKeyedUnarchiver(forReadingWith: datos)
if let alumnoLeido = try unarchiver.decodeTopLevelDecodable(Alumno.self,
forKey: NSKeyedArchiveRootObjectKey) {
print(alumnoLeido.nombre) //Pepe
}
En realidad estas clases existen desde antes de la introducción de
Codable
. Se usaban para guardar datos mediante el mecanismo que existía anteriormente en iOS, denominadoNSCoding
, y se extendieron para poder trabajar también conCodable
.
Configurar Codable¶
En algunas ocasiones nos puede interesar serializar/deserializar los datos usando nombres de campos distintos a los que usamos en nuestras estructuras de datos. Por ejemplo en muchas ocasiones nos tendremos que comunicar con servicios REST cuyo JSON use nombres que nos pueden resultar extraños en nuestro código, o que no se adaptan a las convenciones de Swift.
La asociación entre los nombres de los campos en el formato serializado y en nuestro código se puede definir en una enumeración de String
s llamada CodingKeys
que debe ser conforme al protocolo CodingKey
.
Supongamos que en el ejemplo del struct Alumno
queremos que en nuestra estructura de datos la fecha se siga llamando fechaNacimiento
pero en el formato serializado sea fecha_nacimiento
. Lo haríamos del siguiente modo:
struct Alumno {
var nombre : String
var nota: Float
var fechaNacimiento: Date
private enum CodingKeys : String, CodingKey {
case nombre
case nota
case fechaNacimiento = "fecha_nacimiento"
}
}