Respuesta corta
En lugar de acceder self
directamente, debe acceder indirectamente, desde una referencia que no se retendrá. Si no está utilizando el conteo automático de referencia (ARC) , puede hacer esto:
__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
La __block
palabra clave marca las variables que se pueden modificar dentro del bloque (no lo estamos haciendo), pero tampoco se retienen automáticamente cuando se retiene el bloque (a menos que esté utilizando ARC). Si hace esto, debe estar seguro de que nada más intentará ejecutar el bloque después de que se libere la instancia de MyDataProcessor. (Dada la estructura de su código, eso no debería ser un problema). Lea más sobre__block
.
Si está utilizando ARC , se conservará la semántica de los __block
cambios y la referencia, en cuyo caso deberá declararla __weak
.
Respuesta larga
Digamos que tienes un código como este:
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
El problema aquí es que self retiene una referencia al bloque; mientras tanto, el bloque debe retener una referencia a sí mismo para obtener su propiedad de delegado y enviarle un método. Si todo lo demás en su aplicación libera su referencia a este objeto, su recuento de retención no será cero (porque el bloque lo señala) y el bloque no está haciendo nada malo (porque el objeto lo señala) y así el par de objetos se filtrará en el montón, ocupando memoria pero inalcanzable para siempre sin un depurador. Trágico, de verdad.
Ese caso podría solucionarse fácilmente haciendo esto en su lugar:
id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
[progressDelegate processingWithProgress:percentComplete];
}
En este código, self retiene el bloque, el bloque retiene al delegado y no hay ciclos (visibles desde aquí; el delegado puede retener nuestro objeto, pero eso está fuera de nuestras manos en este momento). Este código no correrá el riesgo de una fuga de la misma manera, porque el valor de la propiedad delegada se captura cuando se crea el bloque, en lugar de buscarlo cuando se ejecuta. Un efecto secundario es que, si cambia el delegado después de crear este bloque, el bloque seguirá enviando mensajes de actualización al antiguo delegado. Si eso puede suceder o no depende de su aplicación.
Incluso si fue genial con ese comportamiento, aún no puede usar ese truco en su caso:
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
Aquí está pasando self
directamente al delegado en la llamada al método, por lo que debe ingresarlo en algún lugar. Si tiene control sobre la definición del tipo de bloque, lo mejor sería pasar el delegado al bloque como parámetro:
self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};
Esta solución evita el ciclo de retención y siempre llama al delegado actual.
Si no puede cambiar el bloqueo, puede lidiar con él . La razón por la que un ciclo de retención es una advertencia, no un error, es que no necesariamente deletrean la fatalidad para su aplicación. SiMyDataProcessor
es capaz de liberar los bloques cuando se completa la operación, antes de que su padre intente liberarlo, el ciclo se interrumpirá y todo se limpiará correctamente. Si puede estar seguro de esto, lo correcto sería utilizar a #pragma
para suprimir las advertencias para ese bloque de código. (O utilice un indicador del compilador por archivo. Pero no desactive la advertencia para todo el proyecto).
También puede considerar usar un truco similar arriba, declarar una referencia débil o no retenida y usarla en el bloque. Por ejemplo:
__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Los tres anteriores le darán una referencia sin retener el resultado, aunque todos se comportan de manera un poco diferente: __weak
intentará poner a cero la referencia cuando se libere el objeto; __unsafe_unretained
te dejará con un puntero no válido; __block
en realidad agregará otro nivel de indirección y le permitirá cambiar el valor de la referencia desde dentro del bloque (irrelevante en este caso, ya quedp
que no se usa en ningún otro lugar).
Lo mejor dependerá de qué código puede cambiar y qué no puede. Pero espero que esto te haya dado algunas ideas sobre cómo proceder.