Serialización usando Foundation Framework

abril 1, 2010

Un factor común en casi todos los programas que desarrollamos es que necesitamos guardar informacion que persista entre ejecuciones. A la hora de realizar esta tarea tenemos varias alternativas, una de las mas comunes es utilizar una base de datos, como podria ser SQLite, o también podemos usar ficheros XML.

Si nuestro programa esta desarrollado con un lenguaje orientado a objetos, es muy posible que lo que queramos grabar sean datos pertenecientes a un objeto, en este caso, hay multitud de APIs que nos permiten serializar los datos de un objeto para recuperarlos posteriormente. Hablando de Cocoa, la plataforma nos brinda un elegante método para conseguir este objetivo.

Lo primero que debemos hacer con el objeto que queremos serializar es hacerle que implemente la interfaz «NSCoding«, al implementar esta interfaz, debemos sobrecargar los métodos initWithCoder y encodeWithCoder. Estos dos mensajes se llamarán cuando queramos guardar el objeto a disco, o cuando estemos reconstruyendolo desde disco.

Cuando una de estas dos cosas ocurre, el funcionamiento típico es que cada objeto va sucesivamente enviando dichos mensajes a los atributos que componen el objeto. De esta manera si nuestro objeto tiene objetos del tipo NSString o NSInteger, debemos pasarles el mensaje a dichas clases. La ventaja es que los tipos básicos de Cocoa como NSString o NSArray ya implementan esta interfaz.

Como ejemplo vamos a serializar una clase que guarda la información de un correo electrónico:

@interface EMail : NSObject <NSCoding>
{

	NSString *		subject;
	NSDate *		mailDate;
	MailContact * 	sender;
	MailContact *	recipient;

	// MailContact es una clase que consta de dos cadenas, nombre y dirección.
}

Además de estos métodos, el framework Foundation nos proporciona tres clases básicas, estas clases son NSCoder y NSKeyedArchived / NSKeyedUnarchiver.

Con NSCoder podremos codificar objetos para ser guardados. Cuando el framework invoca a nuestros métodos de serialización, nos pasara una instancia de la clase NSCoder lista para utilizarla. La clase NSCoder nos proporciona métodos para codificar los tipos básicos como int, float o booleanos, pero tambíen nos proporciona el método encodeObject:, con el que codificaremos los objetos que forman el conjunto de atributos del objeto que tratamos de serializar.

De esta manera nuestros métodos serán algo como:

- (void)encodeWithCoder:(NSCoder *)coder
{
	[coder encodeObject:subject forKey:@"mailSubject"];
	[coder encodeObject:mailDate forKey:@"mailDate"];
	[coder encodeObject:sender forKey:@"mailSender"];
	[coder encodeObject:recipient forKey:@"mailRecipient"];
}

- (id)initWithCoder:(NSCoder *)coder
{
	subject = [[coder decodeObjectForKey:@"mailSubject"] retain];
	mailDate = [[coder decodeObjectForKey:@"mailDate"] retain];
	sender = [[coder decodeObjectForKey:@"mailSender"] retain];
	recipient = [[coder decodeObjectForKey:@"mailRecipient"] retain];

	return self;
}

Como vemos el funcionamiento es bastante sencillo.

Solo nos queda una última pieza en el puzle, y esta es guardar y recuperar los objetos. Para ello entra en juego NSKeyedArchiver y NSKeyedUnarchiver. Estas clases tienen dos métodos que van a realizar esta tarea, estos métodos son: «archiveRootObject:toFile:» y «unArchiveObjectWithFile:«. Al primero le pasaremos el objeto que queremos guardar y el fichero donde lo guardaremos. Mientras que al segundo le pasaremos el fichero y nos devolverá una instancia del objeto.

El código para realizar esto queda algo como:

MailContact* senderContact = [[MailContact alloc] initWithAddress:@"sender@sender.com" Name:@"Sender Contact"];

MailContact* recipientContact = [[MailContact alloc] initWithAddress:@"recipientrecipient.com" Name:@"Recipient Contact"];

EMail* emailPreArchived =
[[EMail alloc] initWithSubject:@"Email de prueba" date:[NSDate date]  sender: senderContact recipient:recipientContact];

NSLog(@"Email created:\n %@\n", emailPreArchived);
NSLog(@"Archiving Object\n");
[NSKeyedArchiver archiveRootObject:emailPreArchived toFile:@"/tmp/pruebaArchive"];
NSLog(@"Retrieving object from storage\n");

EMail* emailRetrieved;
emailRetrieved = [NSKeyedUnarchiver unarchiveObjectWithFile:@"/tmp/pruebaArchive"];
NSLog(@"Email retrieved:\n %@\n", emailRetrieved);

Como vemos guardar nuestros objetos usando el framework Foundation es bastante sencillo, si bien si nuestra estructura de objetos es mas compleja, los metodos de codificación y decodificación serán algo mas complejos. Quizás la principal desventaja de usar este tipo de métodos es la portabilidad, ya que, en algunos casos leer los objetos serializados desde otro entorno diferente es realmente dificil.