Currently browsing: September 2011

Actualización 2.0.1 de Music Maniac

This post was published 7 years ago. It may be exremely outdated.

Icono de Music Maniac Lite 2.0En cuestión de horas estará disponible la versión 2.0.1 de Music Maniac en el AppStore. Esta actualización corrige algunos errores de la versión 2.0, entre los que se encuentran:

  • Un error en la traducción al español.
  • Errores diversos al tener pocas canciones en la biblioteca de iTunes (o pocos metadatos sobre las mismas).
  • Un error que no iluminaba la respuesta correcta al terminar una ronda y no seleccionar ninguna canción.
  • Se ha mejorado el consumo de capacidad de almacenamiento que requiere Music Maniac para guardar ciertas estadísticas.

También incluye otros cambios menores, como que al jugar Offline y salir de un concurso, en lugar de perder los avances, se carga la vista de estadísticas y se guardan los puntos obtenidos durante el tiempo que se jugó.

Además esta actualización también afecta a Music Maniac Lite, pudiendo disfrutar de las novedades de la versión 2.0 en la versión reducida de Music Maniac.

Guardar objetos en archivos con Objective-C

This post was published 7 years ago. It may be exremely outdated.

Ayer Carmen Parra preguntaba en un comentario cómo se pueden guardar los datos introducidos por el usuario en un archivo. Como el proceso es demasiado largo como para publicarlo en un comentario, he decidido escribir este pequeño tutorial.

NSArray, NSDictionary, NSString, NSNumber, NSData y otros

Resultado del código de ejemploHay dos métodos sencillos para almacenar un objeto en un archivo y poder recuperarlo cuando nos convenga. El primero no se puede aplicar a cualquier objeto, sino a objetos de algunas clases de Objective-C que responden a dos métodos llamados writeToFile:atomically: y writeToURL:atomically:. El uso de estos métodos es muy sencillo, basta con llamarlos en el momento en el que queramos que el objeto se guarde en un archivo.

A continuación teneís un ejemplo comentado. Básicamente creamos una matriz, le añadimos algo de contenido, la imprimimos, la guardamos en un archivo y nos deshacemos de ella. A continuación creamos otra matriz que lea el contenido del archivo y comprobamos que tiene el mismo contenido que la anterior. El resultado que obtendréis será similar al de la imagen de la derecha.

NSMutableArray *array = [[NSMutableArray alloc] init]; // Creamos una matriz
	[array addObject:@"¡Hola"]; // Añadimos un objeto
	[array addObject:@"mundo!"]; // Añadimos otro objeto
	NSLog(@"Matriz original: %@", array); // Imprimimos la matriz original
	[array writeToFile:@"filename" atomically:YES]; // Escribimos en el archivo filename el contenido de la matriz
	[array release]; // Liberamos memoria
	NSMutableArray *arrayFromFile = [[NSMutableArray alloc] initWithContentsOfFile:@"filename"]; // Esta matriz tiene el mismo contenido que tenía la matriz anterior
	NSLog(@"%@", arrayFromFile); // Imprimimos la matriz recuperada del archivo
	[arrayFromFile release]; // Liberamos memoria

Del mismo modo que se puede guardar en un archivo un objeto de la clase NSArray, también se puede usar con objetos NSDictionary, NSString, NSData y NSNumber, entre otros. Sin embargo no se puede usar con objetos de una clase creada por nosotros (en la mayoría de los casos).

Clases creadas por nosotros

Para guardar en un archivo un objeto de una clase creada por nosotros, tendremos que hacer que el objeto sigua el protocolo NSCoding. Tras esto, mediante las clases NSKeyedArchiver y NSKeyedUnarchiver podremos guardar en un archivo y leer de un archivo nuestro objeto. Este protocolo tiene dos métodos necesarios: initWithCoder: y encodeWithCoder:.

Vamos a suponer que hemos creado la clase Empleado, que tiene la siguiente interfaz:

@interface Empleado : NSObject
@property (nonatomic, retain)	NSString	*nombre, *apellidos;
@property						NSUInteger	edad;
@property						BOOL		esEmpleadoDelMes;
@end

Para poder almacenar este objeto en un archivo tendremos primero que hacer que siga el protocolo NSCoding. Modficaremos la interfaz y la dejaremos así:

#define kNombre				@"Nombre"
#define kApellidos			@"Apellidos"
#define kEdad				@"Edad"
#define kEsEmpleadoDelMes	@"esEmpleadoDelMes"
@interface Empleado : NSObject
@property (nonatomic, retain)	NSString	*nombre, *apellidos;
@property						NSUInteger	edad;
@property						BOOL		esEmpleadoDelMes;
@end

Básicamente hay dos cambios: hemos indicado que la clase sigue el protocolo NSCoding y hemos definido una serie de constantes que usaremos más adelante y que entenderéis en seguida su utilidad.

Ahora pasamos a la implementación. Esta es la implementación original de la clase Empleado, antes de seguir el protocolo NSCoding:

@implementation Empleado
@synthesize nombre, apellidos;
@synthesize edad;
@synthesize esEmpleadoDelMes;
- (void)dealloc
{
	[nombre release];
	[apellidos release];
	[super dealloc];
}
@end

Ahora vamos a añadir los dos métodos que necesitamos para seguir el protocolo NSCoding. Estos métodos nos permiten archivar nuestro objeto. El método encodeWithCoder: tiene por parámetro un objeto de la clase NSCoder. En este objeto archivaremos las propiedades del nuestro, asignándole a una serie de claves las propiedades del objeto.

Por ejemplo, si queremos hacer que la propiedad nombre se archive en la clave clave, el método tendría el siguiente aspecto:

- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.nombre forKey:@"clave"];
}

No todas las propiedades se archivan de la misma forma. Los objeto se archivan a través del método encodeObject:forKey:, los números enteros se archivan con encodeInteger:forKey: y los valores booleanos con encodeBool:forKey:. Las claves son muy importantes, ya que el método initWithCoder: va a desarchivar el objeto usando las mismas claves para acceder a las propiedades, por eso hemos definido una serie de constantes que serán las claves que usaremos para archivar y desarchivar las propiedades.

Nota: Tenéis una lista de todos los métodos para archivar objetos, así como una explicación del proceso de archivar y desarchivar objetos en la documentación de Apple (en inglés).

Nuestro método encodeWithCoder: quedará así:

- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.nombre forKey:kNombre];
	[coder encodeObject:self.apellidos forKey:kApellidos];
	[coder encodeInteger:self.edad forKey:kEdad];
	[coder encodeBool:self.esEmpleadoDelMes forKey:kEsEmpleadoDelMes];
}

El método initWithCoder: es muy similar al anterior, sólo que su funcionamiento es el contrario: a partir de un objeto archivado se obtienen las propiedades y se crea un objeto de la clase Empleado (en este caso) con las propiedades que tenía el objeto al archivarse. En este caso enviaremos el mensaje decodeObjectForKey:@”clave” a coder y asignaremos el valor devuelto a la propiedad de nuestro objeto. De nuevo, se usan métodos diferentes para decodificar número enteros, valores booleanos y otros tipos de variables. Nuestro método initWithCoder: quedará así:

- (id)initWithCoder:(NSCoder *)coder {
    if ((self = [super init])) {
		self.nombre = [coder decodeObjectForKey:kNombre];
		self.apellidos = [coder decodeObjectForKey:kApellidos];
		self.edad = [coder decodeIntForKey:kEdad];
		self.esEmpleadoDelMes = [coder decodeBoolForKey:kEsEmpleadoDelMes];
	}
    return self;
}

Resultado del ejemploAhora pasemos a crear un objeto Empleado, a guardarlo en un archivo y a comprobar que funciona correctamente. Primero incluímos la interfaz de la clase Empleado en el archivo en el que vamos a crear el objeto, en mi caso, y al tratarse de una “Command Line Tool“, lo incluiré en el archivo main.m. El siguiente código comentado crea un objeto de la clase Empleado, le asigna algunos valores sus propiedades, lo archiva y después crea un nuevo objeto con el contenido del archivo en el que se ha guardado antes el objeto:

Empleado *trabajador = [[Empleado alloc] init]; // Creamos el objeto
	trabajador.nombre = @"Juan"; // Le damos un nombre
	trabajador.apellidos = @"García López"; // Unos apellidos
	trabajador.edad = 26; // Una edad
	trabajador.esEmpleadoDelMes = YES; // Y lo hacemos empleado del mes
	NSLog(@"%@ %@ tiene %lu años", trabajador.nombre, trabajador.apellidos, trabajador.edad); // Imprimimos sus datos
	[NSKeyedArchiver archiveRootObject:trabajador toFile:@"empleado"]; // Lo guardamos en el archivo empleado
	[trabajador release]; // Liberamos memoria
	Empleado *trabajadorRecuperado = [NSKeyedUnarchiver unarchiveObjectWithFile:@"empleado"]; // Creamos un objeto a partir del archivo empleado
	NSLog(@"%@ %@ tiene %lu años", trabajadorRecuperado.nombre, trabajadorRecuperado.apellidos, trabajadorRecuperado.edad); // Imprimimos sus datos

Así se puede guardar un objeto de cualquier clase en un archivo, de forma que puede ser recuperado en cualquier momento. Los objetos más complejos pueden necesitar métodos más complejos a la hora de archivarlos. Del mismo modo no se tienen por qué archivar todas las propiedades si no se quiere.

Por último dejo a vuestra disposición los archivos del proyecto que he creado mientras escribía el tutorial, por si os atascáis y necesitáis un empujoncito.