objective c - Bloque recursivo retener ciclos
objective-c recursion (6)
Aquí hay una solución moderna al problema:
void (^myBlock)();
__block __weak typeof(myBlock) weakMyBlock;
weakMyBlock = myBlock = ^void(int i) {
void (^strongMyBlock)() = weakMyBlock; // prevents the block being delloced after this line. If we were only using it on the first line then we could just use the weakMyBlock.
if (i == 0)
return;
NSLog(@"%d", i);
strongMyBlock(i - 1);
};
myBlock(10);
¿Esto llevará a algún tipo de ciclo de retención? ¿Es seguro de usar?
__block void (^myBlock)(int) = [^void (int i)
{
if (i == 0)
return;
NSLog(@"%d", i);
myBlock(i - 1);
} copy];
myBlock(10);
myBlock = nil;
Hay una solución simple que evita el ciclo y la necesidad potencial de copiar prematuramente:
void (^myBlock)(id,int) = ^(id thisblock, int i) {
if (i == 0)
return;
NSLog(@"%d", i);
void(^block)(id,int) = thisblock;
block(thisblock, i - 1);
};
myBlock(myBlock, 10);
Puede agregar una envoltura para recuperar el tipo de firma original:
void (^myBlockWrapper)(int) = ^(int i){ return myBlock(myBlock,i); }
myBlockWrapper(10);
Esto se vuelve tedioso si quiere extenderlo para hacer recursión mutua, pero no puedo pensar en una buena razón para hacerlo en primer lugar (¿no sería más claro una clase?).
No, eso no causará un ciclo de retención. La palabra clave __block
le dice al bloque que no copie myBlock
, lo que habría ocurrido antes de la asignación causando que la aplicación se bloquee. Si no es ARC, lo único que deberá hacer es liberar myBlock
después de llamar a myBlock(10)
.
Quería una solución que no recibiera advertencias, y en este hilo https://.com/a/17235341/259521 Tammo Freese ofrece la mejor solución:
__block void (__weak ^blockSelf)(void);
void (^block)(void) = [^{
// Use blockSelf here
} copy];
blockSelf = block;
// Use block here
Su explicación tiene perfecto sentido.
Si está utilizando ARC, tiene un ciclo de retención, porque el bloque retiene las variables de objeto __block
. Así que el bloque se retiene. Puedes evitarlo declarando myBlock
como __block
y __weak
.
Si está utilizando MRC, no se retienen las variables de objeto __block
, y no debería tener ningún problema. Solo recuerda liberar myBlock
al final.
Su código contiene un ciclo de retención, pero puede interrumpir el ciclo de retención al final de la recursión configurando myBlock
en nil en el caso base de recursión ( i == 0
).
La mejor manera de demostrarlo es probarlo, ejecutándose en el instrumento Asignaciones, con "Descartar datos no registrados al detenerse" desactivado, "Recuento de referencias de registros" activado y "Solo seguimiento de asignaciones activas" desactivado.
Creé un nuevo proyecto de Xcode usando la plantilla de la herramienta de línea de comandos OS X. Aquí está el programa completo:
#import <Foundation/Foundation.h>
void test() {
__block void (^myBlock)(int) = [^void (int i){
if (i == 0) {
// myBlock = nil;
return;
}
NSLog(@"myBlock=%p %d", myBlock, i);
myBlock(i - 1);
} copy];
myBlock(10);
}
int main(int argc, const char * argv[])
{
@autoreleasepool {
test();
}
sleep(1);
return 0;
}
Luego lo ejecuté en el instrumento Asignaciones, con la configuración que describí anteriormente. Luego cambié "Statistics" a "Console" en Instruments, para ver el resultado del programa:
2012-10-26 12:04:31.391 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 10
2012-10-26 12:04:31.395 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 9
2012-10-26 12:04:31.396 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 8
2012-10-26 12:04:31.397 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 7
2012-10-26 12:04:31.397 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 6
2012-10-26 12:04:31.398 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 5
2012-10-26 12:04:31.398 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 4
2012-10-26 12:04:31.399 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 3
2012-10-26 12:04:31.400 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 2
2012-10-26 12:04:31.401 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 1
<End of Run>
Copié la dirección del bloque ( 0x7ff142c24700
), cambié "Consola" a "Lista de objetos" y pegué la dirección en el cuadro de búsqueda. Los instrumentos me mostraron solo la asignación para el bloque:
El punto debajo de la columna Vivo significa que el bloque aún estaba asignado cuando el programa salió. Se filtró Hice clic en la flecha junto a la dirección para ver el historial completo de la asignación del bloque:
Solo una cosa sucedió con esta asignación: fue asignada.
A continuación, descomenté la línea myBlock = nil
en la instrucción if (i == 0)
. Luego lo pasé de nuevo bajo el perfilador. El sistema aleatoriza las direcciones de memoria por seguridad, así que borré la barra de búsqueda y luego volví a revisar la Consola para ver la dirección del bloque en esta ejecución. Fue 0x7fc7a1424700
esta vez. 0x7fc7a1424700
nuevo a la vista "Lista de objetos" y pegué en la nueva dirección, 0x7fc7a1424700
. Esto es lo que vi:
No hay un punto debajo de la columna Vivo esta vez, lo que significa que el bloque se había liberado en el momento en que se cerró el programa. Luego hice clic en la flecha junto a la dirección para ver el historial completo:
Esta vez, el bloque fue asignado, liberado y liberado.