Correção do aviso “Capturar [um objeto] fortemente neste bloco provavelmente levará a um ciclo de retenção” no código habilitado para ARC

141

No código habilitado para ARC, como corrigir um aviso sobre um potencial ciclo de retenção ao usar uma API baseada em bloco?

O aviso:
Capturing 'request' strongly in this block is likely to lead to a retain cycle

produzido por este trecho de código:

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

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

O aviso está vinculado ao uso do objeto requestdentro do bloco.

Guillaume
fonte
1
Você provavelmente deveria estar usando em responseDatavez de rawResponseData, verifique a documentação do ASIHTTPRequest.
0xced

Respostas:

165

Respondendo a mim mesmo:

Meu entendimento da documentação diz que o uso da palavra block- chave e a definição da variável como nula após a utilização dentro do bloco devem estar ok, mas ainda mostra o aviso.

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

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

    }];

Update: conseguiu que ele funcionasse com a palavra-chave '_ fraco' em vez de ' _block' e usando uma variável temporária:

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

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

Se você também deseja segmentar o iOS 4, use em __unsafe_unretainedvez de __weak. Mesmo comportamento, mas o ponteiro permanece oscilando em vez de ser definido automaticamente como nulo quando o objeto é destruído.

Guillaume
fonte
8
Com base nos documentos do ARC, parece que você precisa usar o __unsafe_unretained __block para obter o mesmo comportamento de antes ao usar o ARC e os blocos.
Hunter
4
@SeanClarkHess: Quando eu combinar as duas primeiras linhas, eu recebo este aviso: "Atribuir retidos objeto a variável fraco; objeto será liberado após a atribuição"
Guillaume
1
@ Guillaume, obrigado pela resposta, de alguma forma, ignorei a variável temporária, tentei isso e os avisos sumiram. Você sabe por que isso funciona? Está apenas enganando o compilador para suprimir os avisos ou o aviso não é mais válido?
22411 Chris
2
Eu postei uma pergunta de acompanhamento: stackoverflow.com/questions/8859649/…
barfoon
3
Alguém pode explicar por que você precisa das palavras-chave __block e __weak? Eu acho que existe um ciclo de retenção sendo criado, mas eu não o vejo. E como a criação de uma variável temporária corrige o problema?
precisa saber é o seguinte
50

O problema ocorre porque você está atribuindo um bloco a uma solicitação que possui uma referência forte a ser solicitada. O bloco reterá automaticamente a solicitação, para que a solicitação original não seja desalocada por causa do ciclo. Faz sentido?

É estranho porque você está marcando o objeto de solicitação com __block para que ele possa se referir a si mesmo. Você pode corrigir isso criando uma referência fraca ao lado dele.

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

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:wrequest.rawResponseData error:nil];
    // ...
    }];
ZaBlanc
fonte
__weak ASIHTTPRequest * wrequest = request; não funcionou para mim. Erro ao fornecer usei __block ASIHTTPRequest * blockRequest = request;
Ram G.
13

Causa devido à retenção do eu no bloco. O bloco será acessado a partir de si mesmo, e o próprio será referido no bloco isso criará um ciclo de retenção.

Tente resolver isso criando uma fraca referência 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
fonte
Esta é a resposta correta e deve ser observado como tal
Benjamin
6

Algumas vezes o compilador xcode tem problemas para identificar os ciclos de retenção, portanto, se você tiver certeza de que não retém o completeBlock, poderá colocar um sinalizador do compilador como este:

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

-(void)someMethod {
}
GOrozco58
fonte
1
Alguns podem argumentar que o design é ruim, mas às vezes eu crio objetos independentes que ficam na memória até terminar uma tarefa assíncrona. Eles são retidos por uma propriedade completeBlock, que contém uma forte referência a si próprio, criando um ciclo de retenção intencional. O completeBlock contém self.completionBlock = nil, que libera o conclusãoBlock e interrompe o ciclo de retenção, permitindo que o objeto seja liberado da memória assim que a tarefa for concluída. Sua resposta é útil para ajudar a acalmar os avisos que ocorrem quando faço isso.
Hyperspasm
1
para ser sincero, as chances de alguém estar certo e o compilador estar errado são muito pequenas. Então eu diria que apenas superando as advertências é um negócio arriscado
Max MacLeod
3

Quando tento a solução fornecida pelo Guillaume, tudo fica bem no modo Debug, mas falha no modo Release.

Observe que não use __weak, mas __unsafe_unretained porque meu destino é o iOS 4.3.

Meu código falha quando setCompletionBlock: é chamado no objeto "request": o pedido foi desalocado ...

Portanto, esta solução funciona nos modos Debug e Release:

// 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];        
}];
squall2022
fonte
Solução interessante. Você descobriu por que ele falha no modo Release e não no 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;
// ....

}];

qual a diferença entre as referências __weak e __block?

Emil Marashliev
fonte