Todos sabemos lo que es una FAQ: Frequently Asked Questions; uséase, preguntas formuladas frecuentemente. No obstante, a menudo las respuestas más interesantes son las que corresponden a preguntas formuladas infrecuentemente.
Veamos un par de ellas relacionadas con los bloques de Objective C:
¿Por qué las propiedades de bloques se deben de declarar como copy? ¿Por qué puñetas hay que copiar los bloques?o bien,
¿Por qué cuando declaras un bloque, no añades un asterisco como con todos los objetos de Objective C? ¿Acaso los bloques no son objetos alojados en el heap (montículo) y manipulados mediante punteros?Para responder a esto, tenemos que repasar algunos hechos sobre los bloques de Objective C.
¿Qué encontrarás en este post?
ToggleBloques de Objective-C
Captura de variables
Las variables, cuando son capturadas, lo son como copias constantes. Esto se hace así para protegerlas de posteriores cambios. No importa si posteriormente alguien cambia el valor de una variable ya capturada por el bloque. Para el bloque, ésta siempre tendrá el valor que tenía cuando fue capturada por el mismo. A esto se le llama captura léxica. Si ejecutamos el siguiente código, veremos que los valores que devuelve cada bloque son 1, 2 y 3.// Un typedef para el bloque // www.putasintaxisdebloques.com typedef void(^Thunk)(void); -(void) captureVars{ int i = 1; Thunk block1 = ^{NSLog(@"%d %p",i, &i);}; i = 2; Thunk block2 = ^{NSLog(@"%d %p",i, &i);}; i = 3; Thunk block3 = ^{NSLog(@"%d %p",i, &i);}; block1(); block2(); block3(); }Por si fuera poco, vemos que la dirección de memoria en la que está i es distinta en cada caso: la variable ha sido copiada a otra zona de memoria. La salida es:
2014-02-20 13:11:00.764 TestBlocks[5704:70b] 1 0xbfffcaf4 2014-02-20 13:11:01.743 TestBlocks[5704:70b] 2 0xbfffcad4 2014-02-20 13:11:02.501 TestBlocks[5704:70b] 3 0xbfffcab4La captura léxica, al igual que casi todo, ha sido inventada por la comunidad de Lisp (Santo, Santo, Santo) y llevada a los mortales por Smalltalk. En el caso de ser objetos, estos son retenidos, así que no hay riesgo que sea destruido sin que el bloque se entere. Los programadores funcionales están contentos y satisfechos al asignar una sola vez una variable y no cambiar su valor jamás. No obstante, nosotros somos herejes, inconscientes de la Luz y la Sabiduría del Cálculo Lambda (No hay más Dios que Alonzo Church y John McCarthy su único profeta), y nos regozijamos en el error de reasignar variables. Para esos casos, tenemos la palabra clave __block, que cambia como se almacena una variable capturada. Si a la variable i la marcamos como __block, el resultado será muy distinto:
-(void) blockVars{ __block int i = 1; Thunk block1 = ^{NSLog(@"%d %p",i, &i);}; i = 2; Thunk block2 = ^{NSLog(@"%d %p",i, &i);}; i = 3; Thunk block3 = ^{NSLog(@"%d %p",i, &i);}; NSLog(@"%d %p",i, &i); block1(); block2(); block3(); }y la salida va y resulta que es:
2014-02-20 13:30:17.594 TestBlocks[5813:70b] 3 0xbfffcb00 2014-02-20 13:30:17.595 TestBlocks[5813:70b] 3 0xbfffcb00 2014-02-20 13:30:17.595 TestBlocks[5813:70b] 3 0xbfffcb00 2014-02-20 13:30:17.596 TestBlocks[5813:70b] 3 0xbfffcb00Es decir, todo Jobs accede a la misma variable, en la misma dirección de memoria con el consiguiente riesgo de despelote.
__block hace que las variables se compartan.En el caso de objetos, éstos NO son retenidos, y podrían desaparecer sin que el bloque se entere. Como no podría ser de otra forma, este error/horror también fue creado por Lisp en sus primeras versiones y se le llama captura dinámica. Posteriormente, fue sustituido por la captura léxica en todos los dialectos civilizados de Lisp.
Los bloques son objetos
Los bloques son objetos, pero unos objetos algo rarillos. Para empezar, se almacenan parcialmente en la pila (stack) en vez del montículo (heap) como los demás objetos. El código en sí, se almacena en el montículo, pero las variables capturadas se dejan en la pila. Esto se hace así porque guardar en la pila es mucho más rápido. Sin embargo, ¿qué pasa cuando un bloque se usa fuera del entorno en que ha sido creado? Sus variables capturadas apuntarán a zonas de memoria que ya no son válidas, y al usuario la aplicación se caerá. ¿Cómo podemos evitar esto? Haciendo una copia del bloque. La primera vez que se copia, se llevan los valores de las variables al montículo. Las demás veces son simplemente un retain. Todo esto es válido cuando no estás usando ARC. En este caso, los bloques son por defecto instancias de __NSStackBlock (clase oculta que representa un bloque en la pila). Sin embargo, si usas ARC, los bloques son por defecto instancias de __NSMallocBlock (clase oculta que representa un bloque en el montículo). En este caso todo está en el montículo y ARC se encarga de hacer la copia siempre que haga falta.¿Por qué las propiedades de bloques se deben de declarar como copy? ¿Por qué puñetas hay que copiar los bloques?
Ya sabemos por qué hay que copiar los bloques, porque si no lo hacemos, sus variables capturadas apuntarán a donde no deben una vez que el bloque esté fuera del contexto donde ha sido creado. Ahora bien, si usamos ARC, no hace falta preocuparse por esto, ya que ARC copiará los bloques siempre que haga falta, no sólo en el setter de una propiedad. Por lo tanto, si usas ARC, no tiene ningún sentido declarar una propiedad de tipo bloque de esta forma:// Un typedef para el bloque // www.putasintaxisdebloques.com typedef void(^Thunk)(void); @interface AGTAppDelegate : UIResponder <UIApplicationDelegate> @property (copy, nonatomic) Thunk myBlock; @property (strong, nonatomic) UIWindow *window; @endTiene mucho más sentido declararla como cualquier otro objeto:
// Un typedef para el bloque // www.putasintaxisdebloques.com typedef void(^Thunk)(void); @interface AGTAppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) Thunk myBlock; @property (strong, nonatomic) UIWindow *window; @endAhora bien, por qué puñetas escribo
@property (strong, nonatomic) Thunk myBlock;en vez de
@property (strong, nonatomic) Thunk *myBlock;