Categorías en Objective-C

A menudo tenemos que ampliar el comportamiento de una clase, y muchas de esas veces esa clase es parte de una librería que no esta desarrollada por nosotros. Habitualmente la forma de ampliar este comportamiento es usando la herencia, nuestra clase heredaría de la que queremos ampliar su funcionalidad y implementaría dichos métodos. Esto es lo que la teoría de orientación a objetos nos cuenta, pero existen otros métodos menos acordes a dicha teoría pero que aportan otros detalles que algunas veces pueden ser utiles.

Estas técnicas pasan por definir los nuevos métodos dentro de la propia clase, como si la estuvieramos ampliando. Así por ejemplo podríamos ejecutar un código como

NSString *cadena = [NSString initWithString:@"Hola que tal"];
NSLog(@"Numero de vocales: %@", [cadena cuentaVocales]);

Cuando sabemos que inicialmente la clase NSString no acepta el mensaje “cuentaVocales”.

Como suele ser habitual, estas cosas tienen su lado bueno y su lado malo.

La principal ventaja es que los que usen nuestra librería, que amplia la clase NSString, no tienen que construir ninguna clase nueva para utilizar estos nuevos métodos, lo que facilita la forma que tienen los usuarios para adaptarse a nuestro código, y si nos ponemos exigentes desde el punto de vista de rendimiento nos ahorramos tener que llamar a la mayoría de los métodos de la clase padre siempre a traves de la clase hija.

Otro de los beneficios es que si tenemos ya métodos que admiten parámetros del tipo de la clase padre, usando este forma de extension de clases podremos acceder a los métodos modificados, sin necesidad de cambiar los tipos de los parámetros.

Por ejemplo, si tenemos un metodo como:

void imprimir_estadisticas: (NSString *)cadena
{
  NSLog(@"Numero de letras: %d", [cadena length]);</p>
  // Podriamos añadir lo siguiente, sin necesidad de cambiar el tipo del parametro.</p>
  NSLog(@"Numero de vocales: %d", [cadena contarVocales]);</p>
}

Este concepto no es exclusivo de Objetive-C, de hecho en muchos otros lenguajes se pueden ampliar tambien las clases. Por ejemplo en C# podemos usar la definición parcial de clases para añadir nuevos métodos, mientras que en Javascript por ejemplo, podemos añadir nuevos metodos modificando la propiedad “prototype”.

Ahora que sabemos en que consisten las categorías, vamos a ver como podemos implementarlas usando Objetive C

Por ejemplo, vamos a ver como añadir una categoría que por un lado anadirá un nuevo método a la clase NSString para contar el numero de “aes” minusculas de una cadena; y por otro, sobreescribirá el metodo capitalizedString que ya posee la clase NSString.

Empezemos con el prototipo, la forma de declarar una categoría es:

@interface NSString (PruebaCategorias)
  - (int) countLowerA;
  - (NSString*) capitalizedString;
@end

Donde normalmente va el nombre de nuestra clase, en este caso se coloca la clase que queremos extender, y entre parentesis el nombre de nuestra categoría. Lo que viene a continuación son la definición de los métodos que forman la categoría.

La parte de la implementación es similar a cualquier implementación de una clase, salvo por el nombre de la categoria. Nuestro ejemplo quedaría:

@implementation NSString (PruebaCategorias)

- (int) countLowerA
{
	int cont = 0;
	for ( int i = 0; i < [self length]; i++)
		if ( [self characterAtIndex:i] == 'a' ) cont++;

	return cont;
}

- (NSString *) capitalizedString
{
	return @"HOLA!";
}
@end

Se recomienda usar una nomenclatura fija para nombrar los ficheros que poseen esta definición, esta nomenclatura sería para nuestro ejemplo “NSString+Pruebacategoria.m/h“, es decir, usar el nombre de la clase que se amplía, el caracter ‘+’ y el nombre de nuestra categoría.

Finalmente, si usamos el siguiente código para probar la categoría:

NSString *str = @"Hola mando";
NSLog(@"Capitalized: %@, As: %d\n", [str capitalizedString], [str countLowerA]);

Se nos mostraria por pantalla

"Capitalized: HOLA!, As: 2" // Esta claro que capitalizedString no funciona como esperábamos.

Antes dijimos que esta forma de extender el comportamiento de las clases tenia sus lados buenos y sus lados malos pero al principio sólo contamos los buenos. Una de las grandes desventajas de ampliar el comportamiento de esta manera es que las ampliaciones pueden ocultar métodos ya existentes, como vemos en el ejemplo. Incluso, esto puede pasar de desventaja a peligro de seguridad. En nuestro caso, hemos “ocultado” el método capitalizedString, de tal manera que si una tercera persona usa nuestra liberia, cuando llame a capitalizedString, se encontrará un comportamiento no esperado, poniendose paranoico, incluso capitalizedString podria borrar todos sus Documentos del Disco Duro.

Además dada la interaccion entre las diferentes clases del framework, ocultar un método podria hacer que muchas cosas dejaran de funcionar, ya que cuando esas otras clases llamen al método que hemos ocultado se encontrarán con que no funciona de forma adecuada. Por ejemplo, supongamos que sobreescribimos el método windowWillClose de la clase NSWindow, seguramente obtengamos cosas no deseadas en nuestra aplicación Cocoa despues de hacer esto.

Como vemos, lo que al principio nos parecia una gran idea, ahora vemos que tiene algunos factores que nos indican a estar muy alerta. Particularmente pienso que no es la forma mas “limpia” de hacer las cosas, si uno quiere ampliar el comportamiento de una clase lo mas idoneo es extender dicha clase. Sin embargo en casos muy puntuales, como los citados al principio de la entrada, podría ver justificado su uso.

3 respuestas a Categorías en Objective-C

  1. mercadder dice:

    Sigue escribiendo. Nos interesa tu experiencia.

  2. robjperez dice:

    Gracias!

    Es un placer saber que el trabajo que conlleva escribir sirve para algo!

    Me apunto también tu blog para echarle un ojo.

    Saludos.

  3. […] 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. […]

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: