Bloques en Objective C y Cocoa
Bloques en Objective C
La principal fuente de inspiración de Objective C ha sido Smalltalk. Sin embargo, una de las características más interesantes y originales de Smalltalk seguía faltando: los bloques. Eso hasta hace poco, porque en la especificación de 2.0 de Objective C, éstos fueron incluidos.
¿Qué son los bloques?
Los bloques en Objective C y Smalltalk son «trozos» de código que se pueden guardar en variables, pasar como argumentos, devolver como resultado de un mensaje y ejecutar posteriormente. Es decir, son en el fondo funciones de primer nivel (como en Lisp o cualquier lenguaje funcional), con una sintaxis algo distinta que recuerda los punteros a funciones de C.
Sin embargo, si sólo se tratase de eso, alguien podría decir que los bloques en Objective C son redundantes, ya que con punteros a funciones, se puede hacer todo eso en C de toda la vida, aunque resulte más farragoso y proclive a errores. Los bloques tiene algo más: capturan de forma automática el entorno léxico en el que han sido creados.
Esto quiere decir que si en un método definimos una variable cualquiera (digamos int i = 42) y luego definimos un bloque, dicho bloque podrá hacer referencia a dicha variable. Si pasamos ese bloque a otro objeto, seguirá pudiendo hacer referencia a dicha variable y su valor original. Más adelante veremos eso con más detalle, y su utilidad.
En resumidas cuentas, un bloque es una «closure«.
Uso de los Bloques
En Smalltalk los bloques se utilizan de mil y una formas, incluso para crear expresiones condicionales. Por ejemplo, en Smalltalk, los booleanos no son un tipo «especial». Son objetos, instancias de las clases True o False (ambas descendientes de la clase Boolean). Las expresiones condicionales se obtienen enviando mensajes como:
ifTrue: TrueBlock ifFalse: FalseBlock
En el caso de mandarlo a una instancia de False, solamente ejecutará el FalseBlock.
En Objective C no es una característica fundamental del lenguaje, sino un añadido posterior y su uso no es tan extendido. Aun a si, los bloques son extremadamente útiles para:
- Enumeración de colecciones
- Ordenación (el bloque se proporciona como el método de comparación)
- Notificación (cuando tal cosa ocurra, ejecuta este bloque)
- Gestores de error (si ocurre un error mientras haces esto, ejecuta este bloque)
- Gestores de finalización (cuando termines de hacer esto, ejecuta este bloque)
- Animación de vistas
- Por último, tal vez la aplicación más importante en Cocoa: multitarea mediante «Grand Dispatch Central» (GCD).
Sintaxis de bloques en Objective C & Cocoa
La sintaxis que propuso Apple para añadir bloques a C se vio condicionada por la necesidad de mantener compatibilidad con C++. Por lo tanto, se utilizó el acento circunflejo ^ como indicador de inicio de un bloque.
Al parecer era el único operador unario disponible que no podía ser sobrecargado por C++. Ventajas de tener a un tonto en la familia y tener que sentarlo a la mesa…
Declaración
Normalmente se usa un typedef:
typedef double (^unary_block_t) (double op);
Aquí declaramos un bloque llamado unary_block_t que acepta un argumento llamado op de tipo double y que devuelve un double.
Definición
Aquí es donde escribimos el código que ejecutará el bloque. No hace falta especificar el tipo del valor que devuelve, ya que el compilador lo infiere.
unary_block_t square = ^(double op){ return operand * 2.0; }; // ¡OJO! Hay que cerrar el bloque con punto y coma.
Para el caso muy común en que no hay ni argumentos ni retorno, la sintaxis es bastante abreviada :
^{ //blá; //blá; };
Se llaman como una función cualquiera:
square(42);
Captura del entorno léxico
Las variables locales definidas en el mismo método (y antes que él), son capturadas por el bloque, pero son de sólo lectura dentro del mismo.
Básicamente se hace una copia constante de aquellas variables definidas previamente.
BOOL stoppedEarly = NO; int i = 42; NSDictionary *dict = [NSDictionarydictionaryWithContentsOfURL: [NSURLURLWithString:@"file:///Users/fernando/Junkyard/Vocabulous/vocabwords.txt"]]; assert(dict); [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { static int j = 0; NSLog(@"Value for key %@ is %@", key, obj); j += i; NSLog(@"%d", j); if([@"ENOUGH"isEqualToString:key]){ *stop = YES; //stoppedEarly = YES; // ¡imposible! } }];
Variables de instancia y punteros
Las variables de instancia, al ser punteros, sí que son modificables: lo que es constante es el puntero. ¡El puntero es constante dentro del bloque, no los datos apuntados por él!
Es muy importante saber que cuando usamos un puntero a un objeto dentro de un bloque, dicho objeto automágicamente recibe un mensaje retain. Será liberado (release) cuando el bloque lo sea a su vez.
Métodos que aceptan un bloque
A menudo no se utiliza un typedef. La sintaxis es exactamente la misma a la del typedef, sólo que se omite la palabra typedef y el nombre del typedef:
-(void) enumerateKeysAndObjectsUsingBlock:(void (^) (id key,id obj, BOOL *stop);
Los bloques son objetos limitados
Los bloques en Cocoa son objetos, aunque algo limitados. Puedes enviar la siguiente lista de mensajes a un bloque:
- copy
- retain
- release
- autorelease
Los bloques pueden devolver otros objetos y otros bloques
Ejemplo de bloque que devuelve un objeto:
typedef NSString* (^stringyfier_t)(int x); stringyfier_t str = ^(int x) { return [NSStringstringWithFormat:@"%d", x]; };
Podemos por lo tanto implementar bloques que devuelven otros bloques. Esto nos permite implementar cosas como la función adder que menciona Paul Graham en «ANSI Common Lisp«.
La función adder es una ejemplo de función paramétrica que devuelve una versión especializada de sí misma: adder(42) devuelve otra función que devuelve la suma de 42 y el argumento que reciba:
typedef int (^unary_operation_t)(int x); typedef unary_operation_t(^adder_t)(int n); adder_t adder = ^(int n){ unary_operation_t fn = ^(int x){ return n + x; }; return fn; }; unary_operation_t one = adder(1); NSLog(@"The %d nights", one(1000));
También se podrían implementar los «streams» mencionados en «Structure and Interpretation of Computer Programs» (SICP) para representar secuencias infinitas de datos.