versiones guia español descargar actualizar objective-c ios objective-c-blocks automatic-ref-counting

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];



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.