Se corrigió la advertencia "Es muy probable que capturar [un objeto] en este bloque conduzca a un ciclo de retención" en el código habilitado para ARC

141

En el código habilitado para ARC, ¿cómo corregir una advertencia sobre un posible ciclo de retención, cuando se utiliza una API basada en bloques?

La advertencia:
Capturing 'request' strongly in this block is likely to lead to a retain cycle

producido por este fragmento de código:

ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.rawResponseData error:nil];
    // ...
    }];

La advertencia está vinculada al uso del objeto requestdentro del bloque.

Guillaume
fuente
1
Probablemente debería estar usando en responseDatalugar de rawResponseData, consulte la documentación de ASIHTTPRequest.
0xced

Respuestas:

165

Respondiendo a mi mismo:

Mi comprensión de la documentación dice que usar la palabra clave blocky establecer la variable en nulo después de usarla dentro del bloque debería estar bien, pero aún muestra la advertencia.

__block ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.responseData error:nil];
    request = nil;
// ....

    }];

Actualización: consiguió que funcionara con la palabra clave '_ débil' en lugar de ' _block', y usando una variable temporal:

ASIHTTPRequest *_request = [[ASIHTTPRequest alloc] initWithURL:...
__weak ASIHTTPRequest *request = _request;

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.responseData error:nil];
    // ...
    }];

Si desea apuntar también a iOS 4, use en __unsafe_unretainedlugar de __weak. Mismo comportamiento, pero el puntero permanece colgando en lugar de establecerse automáticamente en nulo cuando se destruye el objeto.

Guillaume
fuente
8
Según los documentos de ARC, parece que necesita usar __unsafe_unretained __block para obtener el mismo comportamiento que antes cuando usa ARC y bloques.
Hunter
44
@SeanClarkHess: cuando combino las dos primeras líneas, aparece esta advertencia: "Asignación de objeto retenido a variable débil; el objeto se liberará después de la asignación"
Guillaume
1
@Guillaume, gracias por la respuesta, de alguna manera pasé por alto la variable temporal, probé eso y las advertencias desaparecieron. ¿Sabes por qué funciona esto? ¿Simplemente está engañando al compilador para suprimir las advertencias o la advertencia ya no es válida?
Chris Wagner
2
He publicado una pregunta de seguimiento: stackoverflow.com/questions/8859649/…
barfoon
3
¿Alguien puede explicar por qué necesita las palabras clave __block y __weak? Supongo que se está creando un ciclo de retención, pero no lo veo. ¿Y cómo la creación de una variable temporal soluciona el problema?
user798719
50

El problema ocurre porque está asignando un bloque a la solicitud que tiene una fuerte referencia para solicitarlo. El bloque retendrá automáticamente la solicitud, por lo que la solicitud original no se desasignará debido al ciclo. ¿Tener sentido?

Es extraño porque está etiquetando el objeto de solicitud con __block para que pueda referirse a sí mismo. Puede solucionar esto creando una referencia débil junto a él.

ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...];
__weak ASIHTTPRequest *wrequest = request;

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:wrequest.rawResponseData error:nil];
    // ...
    }];
ZaBlanc
fuente
__weak ASIHTTPRequest * wrequest = request; no funciono para mi Dando error utilicé __block ASIHTTPRequest * blockRequest = request;
Ram G.
13

Causa debido a retener al yo en el bloque. Se accederá al bloque desde self, y self se refiere en bloque. Esto creará un ciclo de retención.

Intenta resolver esto creando una referencia débil de self

__weak typeof(self) weakSelf = self;

operationManager = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operationManager.responseSerializer = [AFJSONResponseSerializer serializer];
[operationManager setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

    [weakSelf requestFinishWithSucessResponseObject:responseObject withAFHTTPRequestOperation:operation andRequestType:eRequestType];

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    [weakSelf requestFinishWithFailureResponseObject:error withAFHTTPRequestOperation:operation andRequestType:eRequestType];
}];
[operationManager start];
HDdeveloper
fuente
Esta es la respuesta correcta y debe tenerse en cuenta como tal
Benjamin
6

Algunas veces el compilador xcode tiene problemas para identificar los ciclos de retención, por lo que si está seguro de que no retendrá el completeBlock, puede colocar un indicador de compilación como este:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"

-(void)someMethod {
}
GOrozco58
fuente
1
Algunos podrían argumentar que es un mal diseño, pero a veces creo objetos independientes que se quedan en la memoria hasta que terminan con una tarea asincrónica. Son retenidos por una propiedad completeBlock que contiene una fuerte referencia a sí mismo, creando un ciclo de retención intencional. CompleteBlock contiene self.completionBlock = nil, que libera completeBlock y rompe el ciclo de retención, lo que permite que el objeto se libere de la memoria una vez que se completa la tarea. Su respuesta es útil para ayudar a calmar las advertencias que ocurren cuando hago esto.
hiperespasmo
1
Para ser honesto, las posibilidades de que uno tenga la razón y el compilador esté equivocado son muy pequeñas. Así que diría que superar las advertencias es un negocio arriesgado
Max MacLeod
3

Cuando pruebo la solución proporcionada por Guillaume, todo está bien en modo de depuración pero se bloquea en el modo de lanzamiento.

Tenga en cuenta que no use __weak pero __unsafe_unretained porque mi objetivo es iOS 4.3.

Mi código se bloquea cuando setCompletionBlock: se llama al objeto "solicitud": la solicitud se desasignó ...

Por lo tanto, esta solución funciona tanto en modo de depuración como en versión:

// Avoiding retain cycle :
// - ASIHttpRequest object is a strong property (crashs if local variable)
// - use of an __unsafe_unretained pointer towards self inside block code

self.request = [ASIHttpRequest initWithURL:...
__unsafe_unretained DataModel * dataModel = self;

[self.request setCompletionBlock:^
{
    [dataModel processResponseWithData:dataModel.request.receivedData];        
}];
chubasco2022
fuente
Solución interesante ¿Te diste cuenta por qué se bloquea en el modo Release y no en Debug?
Valerio Santinelli
2
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...
__block ASIHTTPRequest *blockRequest = request;
[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:blockRequest.responseData error:nil];
    blockRequest = nil;
// ....

}];

¿Cuál es la diferencia entre __weak y __block de referencia?

Emil Marashliev
fuente