objective c - guia - ¿Cómo evito capturar yo mismo en bloques cuando implemento una API?
qgis manual (8)
Respuesta corta
En lugar de acceder a self
directamente, debe acceder a él indirectamente, desde una referencia que no se conservará. Si no está utilizando el conteo automático de referencias (ARC) , puede hacer esto:
__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
La palabra clave __block
marca las variables que pueden modificarse dentro del bloque (no lo estamos haciendo), pero tampoco se retienen automáticamente cuando se retiene el bloque (a menos que esté usando ARC). Si hace esto, debe asegurarse de que nada más va a 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 acerca de __block
.
Si está utilizando ARC , la semántica de __block
cambia y la referencia se mantendrá, en cuyo caso deberá declararla __weak
en __weak
lugar.
Respuesta larga
Digamos que tienes un código como este:
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
El problema aquí es que el yo está reteniendo una referencia al bloque; Mientras tanto, el bloque debe conservar una referencia a sí mismo para obtener su propiedad de delegado y enviar un método al delegado. 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 hace nada incorrecto (porque el objeto lo señala) y por lo tanto el par de objetos se filtrará en el montón, ocupando memoria pero siempre inalcanzable sin un depurador. Trágico, de verdad.
Ese caso se podría arreglar fácilmente haciendo esto en su lugar:
id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
[progressDelegate processingWithProgress:percentComplete];
}
En este código, el yo retiene el bloque, el bloque retiene al delegado y no hay ciclos (visible desde aquí; el delegado puede retener nuestro objeto, pero eso ya no está en nuestras manos). Este código no correrá el riesgo de una fuga de la misma manera, porque el valor de la propiedad del delegado 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 es probable que suceda o no depende de su aplicación.
Incluso si te portaste bien con ese comportamiento, aún no puedes usar ese truco en tu caso:
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
Aquí te estás pasando directamente al delegado en la llamada de método, por lo que debes obtenerlo 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 un 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 puedes cambiar el bloque, podrías lidiar con él . La razón por la que un ciclo de retención es una advertencia, no un error, es que no necesariamente significan fatalidad para su aplicación. Si MyDataProcessor
puede 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, entonces lo correcto sería usar un #pragma
para suprimir las advertencias para ese bloque de código. (O use un indicador de compilador por archivo. Pero no deshabilite la advertencia para todo el proyecto).
También puede considerar el uso de un truco similar anterior, 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
lo dejará con un puntero no válido; __block
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 que dp
no se usa en ningún otro lugar).
Lo mejor dependerá de qué código puede cambiar y qué no. Pero espero que esto le haya dado algunas ideas sobre cómo proceder.
Tengo una aplicación que funciona y estoy trabajando para convertirla a ARC en Xcode 4.2. Una de las advertencias previas a la verificación consiste en capturarse con fuerza en un bloque que conduce a un ciclo de retención. He hecho un ejemplo de código simple para ilustrar el problema. Creo que entiendo lo que esto significa pero no estoy seguro de la forma "correcta" o recomendada de implementar este tipo de escenario.
- yo es una instancia de la clase MyAPI
- el código siguiente se simplifica para mostrar solo las interacciones con los objetos y bloques relevantes a mi pregunta
- asuma que MyAPI obtiene datos de una fuente remota y MyDataProcessor trabaja en esos datos y produce una salida
- El procesador está configurado con bloques para comunicar progreso y estado.
Ejemplo de código:
// code sample
self.delegate = aDelegate;
self.dataProcessor = [[MyDataProcessor alloc] init];
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
self.dataProcessor.completion = ^{
[self.delegate myAPIDidFinish:self];
self.dataProcessor = nil;
};
// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];
Pregunta: ¿Qué estoy haciendo "mal" y / o cómo debería modificarse esto para cumplir con las convenciones de ARC?
Combinando algunas otras respuestas, esto es lo que uso ahora para que un yo débil tecleado lo use en bloques:
__typeof(self) __weak welf = self;
Establecí eso como un fragmento de código XCode con un prefijo de finalización de "welf" en los métodos / funciones, que aparece después de escribir solo "nosotros".
Creo que la solución sin ARC también funciona con ARC, usando la palabra clave __block
:
EDITAR: De acuerdo con las Notas de la versión de Transición a ARC , un objeto declarado con __block
storage todavía se conserva. Utilice __weak
(preferido) o __unsafe_unretained
(para compatibilidad con versiones anteriores).
// code sample
self.delegate = aDelegate;
self.dataProcessor = [[MyDataProcessor alloc] init];
// Use this inside blocks
__block id myself = self;
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[myself.delegate myAPI:myself isProcessingWithProgress:percentComplete];
};
self.dataProcessor.completion = ^{
[myself.delegate myAPIDidFinish:myself];
myself.dataProcessor = nil;
};
// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];
La nueva forma de hacerlo es usando @weakify y @strongify marco
@weakify(self);
[self methodThatTakesABlock:^ {
@strongify(self);
[self doSomething];
}];
Para una solución común, tengo estos definidos en el encabezado de precompilación. Evita la captura y sigue habilitando la ayuda del compilador evitando usar id
#define BlockWeakObject(o) __typeof(o) __weak
#define BlockWeakSelf BlockWeakObject(self)
Luego en código puedes hacer:
BlockWeakSelf weakSelf = self;
self.dataProcessor.completion = ^{
[weakSelf.delegate myAPIDidFinish:weakSelf];
weakSelf.dataProcessor = nil;
};
Si está seguro de que su código no creará un ciclo de retención, o que el ciclo se romperá más adelante, entonces la forma más sencilla de silenciar la advertencia es:
// code sample
self.delegate = aDelegate;
self.dataProcessor = [[MyDataProcessor alloc] init];
[self dataProcessor].progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
[self dataProcessor].completion = ^{
[self.delegate myAPIDidFinish:self];
self.dataProcessor = nil;
};
// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];
La razón por la que esto funciona es que si bien el acceso a puntos de las propiedades se tiene en cuenta en el análisis de Xcode, y por lo tanto
x.y.z = ^{ block that retains x}
se considera que tiene una retención por x de y (en el lado izquierdo de la asignación) y por y de x (en el lado derecho), las llamadas a métodos no están sujetas al mismo análisis, incluso cuando son llamadas a métodos de acceso a propiedades que son equivalentes al acceso a puntos, incluso cuando esos métodos de acceso a la propiedad son generados por compilador, por lo que en
[x y].z = ^{ block that retains x}
solo se ve que el lado derecho crea una retención (por y de x), y no se genera una advertencia de retención de ciclo.
También existe la opción de suprimir la advertencia cuando esté seguro de que el ciclo se romperá en el futuro:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
#pragma clang diagnostic pop
De esa manera, no tiene que hacer __weak
con __weak
, self
aliasing y explícito prefijo ivar.
warning => "capturar uno mismo dentro del bloque es probable que lleve un ciclo de retención"
cuando se refiere a sí mismo o su propiedad dentro de un bloque que es fuertemente retenido por uno mismo de lo que se muestra en la advertencia anterior.
Así que para evitarlo tenemos que hacer una semana ref.
__weak typeof(self) weakSelf = self;
así que en lugar de usar
blockname=^{
self.PROPERTY =something;
}
deberíamos usar
blockname=^{
weakSelf.PROPERTY =something;
}
nota: el ciclo de retención se produce generalmente cuando algunos objetos se refieren entre sí, por lo que ambos tienen un recuento de referencias = 1 y su método delloc nunca se llama.