Key-Value coding

Seguramente en muchos de vuestros desarrollos os habreis encontrado con las típicas clases que contienen únicamente datos. Por ejemplo, en Java estas clases se llaman POJOs. Normalmente la forma de acceder al contenido de estos datos es usando los métodos de acceso, también llamados getters y setters.

El Key-Value coding (KVC), nos va a permitir acceder a estos datos de forma un poco diferente, y en cierta manera, mucho mas útil y dinámica.

El uso típico de lenguajes como Java o C# es utilizar unicamente los métodos de acceso, sin embargo en otros entornos como los lenguajes de script, se nos abren otras posibilidades para acceder a los datos que encapsula una clase. Con los ultimos cambios que se incorporaron a Objetive-C, el mecanismo para acceder a dichos datos, está mas cerca de los lenguajes de script que de los lenguajes compilados tradicionales.

Gracias a KVC podremos acceder a las variables usando únicamente su nombre, de tal manera que el propio runtime se encargará de llamar a los métodos de acceso adecuados. La forma de acceder a estas variables es usando un par de métodos genericos, getValueForKey: y setValue:forKey:.

La pregunta es. ¿Y para que necesitamos hacer esto de esta manera en lugar de usar los tipicos getters y setters?. Para responderla imaginemonos la siguiente situación, tenemos un conjunto de nombres de propiedades almacenados en un array, si queremos acceder a las propiedades usando los nombres, con estos métodos el acceso sería directo, sin embargo, con el acceso tradicional deberiamos «montar» un conjunto de ifs para analizar cada caso. En código sería algo así:

/* SIN KVC */
// array tiene ["altura", "peso", "edad"]
for (NSString* prop in array) {
	if ( [prop isEqual:@"altura"] ) {
		valor = [persona altura];
		NSLog(@"El valor de altura para la persona es %f", valor);
	} else if ( [prop isEqual:@"peso"] ) {
		valor = [persona peso];
		NSLog(@"El valor de peso para la persona es %f", valor);
	} else if ( [prop isEqual:@"edad"] ) {
		valor = [persona edad];
		NSLog(@"El valor de edad para la persona es %f", valor);
	}
}

/* CON KVC */
for (NSString* prop in array) {
	valor = [persona getValueForKey:prop];
	NSLog(@"El valor de %@ para la persona es: %f", prop, valor);
}

Hablando de un caso práctico, el tipico ejemplo es para rellenar los datos de una tabla, el método encargado de devolver el valor de una columna obtiene el nombre de dicha columna, que habitualmente se corresponde con una propiedad de una clase, usando KVC, el método puede devolver el valor de la propiedad de forma directa. Como vemos en el siguiente fragmento de código

- (id)tableView:(NSTableView *)tableview 
  objectValueForTableColumn:(id) theColumn
                        row:(int) theRow
{
    Persona *p = [arrayPersonas objectAtIndex:theRow];
	return [p valueForKey:[theColumn identifier]];
}

De hecho, este mecanismo de acceso a propiedades es el que se utiliza cuando realizamos un binding en el Interface Builder. Automaticamente liga el valor del control a la propia propiedad del objeto, de tal manera que hacer que una vista muestre el contenido de una clase es bastante trivial.

Y, ¿Cómo hago que mi clase soporte los mecanismos KVC?. Para responder a la pregunta, primero vamos a ver como funciona internamente los mecanismos de KVC. 😉

Cuando enviamos el mensaje getValueForKey: o setValue:forKey:, internamente se busca la existencia de un método de acceso que use la nomenclatura estandar, si por ejemplo, la key es «peso», se buscará un par de métodos llamados «peso» y «setPeso». Si existen, se usarían para acceder a los datos. En el caso de que no existan, se envía el mensaje accessInstanceVariablesDirectly, si este mensaje retorna «YES», se procede a buscar la propia variable dentro del objeto. Si este procedimiento tampoco funciona, entonces se le envía al objeto el mensaje setValue:forUndefinedKey: como última instancia. La implementación por defecto de este método levanta una excepción. Pero el objeto puede sobrecargar esta implementación por defecto para provocar otro comportamiento. Realmente el proceso de acceso para un «get» es algo mas complejo que el que hemos expuesto, pero a grandes rasgos se podría simplificar como en el caso de los «set»

Volviendo a la pregunta, para que nuestra clase soporte el acceso KVC, la forma mas sencilla es proporcionar los métodos estandar. Para realizar esta tarea, tenemos varias opciones. Una de ellas, y las obvia, es hacernos nosotros a mano estos métodos, sin embargo, Objetive-C nos brinda la posibilidad de autogenerar este código.

En esta autogeneración entra en juego dos elementos, @property y @synthesize . @property nos servirá para declarar las propiedades en el fichero .h, mientras que @synthesize nos servirá para definir los métodos de acceso dentro de la sección de implementación. Por ejemplo:

// Persona.h
@interface Persona : NSObject {
}

@property (retain) NSNumber* peso;
@property (retain) NSNumber* altura;
@property (retain) NSNumber* edad;

@end

// Persona.m
@implementation Persona
@synthesize peso, altura, edad;

@end

Únicamente colocando ese código, el compilador generará por nosotros los métodos de acceso. Sin embargo, si necesitamos un procesamiento especial en el acceso a los datos de nuestra clase, tendremos que manualmente, implementar dichos getters y setters.

Seguramente os habreís fijado en el (retain) que esta incluido en la declaración de la propiedad. Lo que colocamos entre parentesis son los atributos de la propiedad e indica que se va a hacer la asignación de la propiedad. El tema es algo complejo y podría ser una entrada en el blog, por lo que de momento, nos podemos quedar en que existen tres tipos exclusivos entre si, retain, assign y copy. Y que hacen un retain sobre la variable, la asignan tal cual o la copian respectivamente.

Muy ligado a todo lo que hemos hablado, esta el acceso «simplificado» a estos campos que se introdujo recientemente en Objetive-C. Este acceso se conoce como «dot syntax». Usando esta técnica podemos acceder a los datos simplemente poniendo un «.» entre la instancia que posee los datos y el nombre del dato, como por ejemplo:

persona.peso = [NSNumber numberWithInt:5];
NSLog(@"El peso es: %@", persona.peso);

Al igual que lo que hemos comentado antes, este acceso realmente esta enmascarando una llamada a setPeso: y peso, de la clase Persona.

Como veis, estos mecanismos simplifican el código, y aprovechandose del dinamismo de Objetive-C, proporcionan cosas tan importantes como son los bindings.

2 Responses to Key-Value coding

  1. Tyflos dice:

    Un artículo muy interesante. Creo que voy a recodificar algunas de mis clases para soportar esto. Estaría bien un artículo explicando más en profundidad el @property y sus diferentes declaraciones. Muchas gracias por tus artículos

Deja un comentario