Serialización de clases

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 su representación como secuencia de bytes. El decoding es el proceso inverso. La codificación puede hacerse en distintos formatos: JSON, XML, datos binarios,...

Los protocolos Encodable y Decodable

Para que un objeto sea serializable (o "archivable") debe implementar estos protocolos. Encodable se encarga de transformar de objeto a secuencia de bytes y Decodable a la inversa.

El protocolo Encodable declara un único método encode:

func encode(to: Encoder) throws

En la mayoría de casos no será necesario escribir nosotros mismos el código de este método, ya que lo generará automáticamente el compilador siempre que nuestra clase esté compuesta por campos que sean Encodables. Muchos tipos básicos de Swift son Encodable, como los String, Int, Float, Date, Array...

Por otro lado, Decodable, que es el protocolo encargado de transformar de secuencia de bytes a objeto, declara un único inicializador:

init(from decoder: Decoder) throws

Al igual que con Encodable, si los campos que componen una clase son campos Decodable, nuestra clase lo será automáticamente y no tendremos que escribir el código del inicializador.

Existe un tercer protocolo, Codable, que no es más que una combinación de ambos, Encodable y Decodable:

typealias Codable = Encodable & Decodable

Los objetos que queramos que sean serializables y deserializables los declararemos como conformes a Codable.

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
}

Para que esta clase sea serializable/deserializable simplemente declaramos que es conforme al protocolo Codable :

struct Alumno : Codable {
    //..el resto es exactamente igual
}

Archivar y “desarchivar”

Las clases NSKeyedArchiver y NSKeyedUnarchiver sirven para "archivar" y "desarchivar" objetos/estructuras, respectivamente. Es decir, convertirlas a secuencias de bytes almacenables en un archivo.

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
}

Cambiar los nombres de los campos

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 Strings 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"
    }
}

Last updated