Métodos privados en Objetive-C

julio 25, 2010

Si programamos habitualmente en Objetive-C seguramente nos habremos encontrado con la necesidad de crear un método que sea solo accesible desde dentro de la clase, vamos, un método privado 🙂

Seguramente, en este momento recordemos como se hace en otros lenguajes, pero no podemos recordar como se hacia en este lenguaje. El motivo de esta falta de recuerdo es por que realmente no nos habremos encontrado una sección sobre ello en el libro o web que usamos para aprender este lenguaje.

Si bien existe la habituales directivas de ámbito @private, @public y @protected de compilador, solo tienen aplicación en los atributos de la clase, y no en los métodos. Además al igual que pasa con otros lenguajes estas directivas solo se aplican en el compilador, pero en el caso de Objetive-C, si accedemos a estas variables privadas, solo obtendremos un warning, como el que vemos en la imagen.

Pero entonces ¿como definimos métodos privados?. Antes de ello vamos a repasar que contiene un fichero de implementación.

Los ficheros .m habitualmente contienen la definición de los métodos que se han declarado en el fichero .h. Como seguramente sabremos, dichas definiciones están comprendidas entre las directivas @implementation CLASE y @end. ¿Que pasa si intentamos definir un método fuera de este bloque?. Pues depende ;), si intentamos definir un método de la clase, el compilador no nos dejara, pero si por otro lado tratamos de definir una función de C o C++, el compilador no pondrá ningún problema. De esta manera podemos pensar que la sección entre @implementation y @end es la parte Objetive-C del fichero .m. Fuera de esta sección no podremos acceder a cosas como “self”, aunque si podremos meter sin problemas código en Objetive-C. Por ejemplo, veamos el contenido del siguiente fichero .m.

int sumaC(int a, int b) {
	PruebaPrivado *pp = [[PruebaPrivado alloc] init];
	return [pp suma:a with:b];
}

@implementation PruebaPrivado
-(int) suma: (int) op1 with: (int) op2 {
	return op1 + op2;
}

-(int) suma2: (int) op1 with: (int) op2 {
	return sumaC(op1, op2);
}

@end

En Objetive-C se sigue una filosofía parecida. La primera aproximación de método privado pasa por declarar esa función únicamente en el fichero .m, y no en el .h. De esta manera los clientes de la clase importarán en fichero .h, y no sabrán de la existencia de los métodos privados. Eso si, como hemos visto, si queremos acceder al contenido de la clase, debe estar definida dentro del bloque de implementación. Como podemos ver en el siguiente ejemplo.

@implementation PruebaPrivado

-(int) suma: (int) op1 with: (int) op2 {
	return [self sumaPrivada: op1 with: op2];
}

-(int) sumaPrivada: (int)o1 with: (int) o2 {
	return o1 + o2;
}

@end

Sin embargo, esta forma de hacerlo tiene sus inconvenientes, por un lado, si compilamos el código del ejemplo el compilador nos dará un warning. Este warning viene de que sumaPrivada no esta definido en el momento de compilar el método suma. Por lo que el compilador nos informa de que PruebaPrivado puede no responder al mensaje sumaPrivada. Este warning se solucionaría poniendo la declaración del método antes de su uso, como pasa habitualmente con C.

Pero aún sin el warning, otro principal inconveniente es la legibilidad. Si intercalamos métodos privados con públicos, mas adelante puede ser un follón revisar nuestro propio código, ya que no tendremos agrupadas todas las funciones privadas de nuestra clase.

Aunque las solución anterior presentaba ciertos inconvenientes, sienta las bases de la solución que habitualmente se suele usar. Con la incorporación de las categorías en Objetive-C se abre la posibilidad de modificar una clase “a posteriori” como ya vimos en esta otra entrada.

La solución pasa por crear una categoría en el fichero .m de implementación, que no estará visible por ningún cliente y que agrupara los métodos privados, de esta manera todos los métodos estarán declarados en el mismo lugar, y nos quitaremos tanto el problema del warning del compilador como la desorganización.

Por tanto, el código quedara como:

@interface PruebaPrivado ()

-(int) sumaPrivada:(int)o1 with:(int)o2;

@end


@implementation PruebaPrivado

-(int) suma: (int) op1 with: (int) op2 {
	return [self sumaPrivada: op1 with: op2];
}

-(int) sumaPrivada: (int)o1 with: (int) o2 {
	return o1 + o2;
}

@end

Como vemos, ahora sumaPrivada esta declarada dentro de la categoría, que además como habéis visto, no lleva nombre. Este hecho nos aporta alguna ventaja como es que la definición de los métodos puede ir en la misma sección de implementación que los métodos públicos, en lugar de tener que crear una sección especifica para esta categoría.

Anuncios

Key-Value coding

julio 17, 2010

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.