Correction de l'avertissement «La capture [d'un objet] fortement dans ce bloc est susceptible de conduire à un cycle de conservation» dans le code ARC

141

Dans le code ARC activé, comment corriger un avertissement concernant un cycle de conservation potentiel, lors de l'utilisation d'une API basée sur des blocs?

L'avertissement:
Capturing 'request' strongly in this block is likely to lead to a retain cycle

produit par cet extrait de code:

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

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

L'avertissement est lié à l'utilisation de l'objet requestà l'intérieur du bloc.

Guillaume
la source
1
Vous devriez probablement utiliser à la responseDataplace de rawResponseData, consultez la documentation ASIHTTPRequest.
0xcédé le

Réponses:

165

Me répondant:

Ma compréhension de la documentation dit que l'utilisation du mot block- clé et la définition de la variable sur nil après l'avoir utilisée dans le bloc devraient être acceptées, mais cela affiche toujours l'avertissement.

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

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

    }];

Mise à jour: je l'ai fait fonctionner avec le mot-clé '_ faible' au lieu de ' _block', et en utilisant une variable temporaire:

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

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

Si vous souhaitez également cibler iOS 4, utilisez à la __unsafe_unretainedplace de __weak. Même comportement, mais le pointeur reste suspendu au lieu d'être automatiquement mis à nil lorsque l'objet est détruit.

Guillaume
la source
8
D'après les documents ARC, il semble que vous deviez utiliser __unsafe_unretained __block ensemble pour obtenir le même comportement qu'auparavant lors de l'utilisation d'ARC et de blocs.
Hunter le
4
@SeanClarkHess: Lorsque je combine les deux premières lignes, j'obtiens cet avertissement: "Assigner un objet conservé à une variable faible; l'objet sera libéré après l'assignation"
Guillaume
1
@Guillaume merci pour la réponse, certains comment j'ai oublié la variable temporaire, essayé cela et les avertissements sont partis. Savez-vous pourquoi cela fonctionne? Est-ce simplement duper le compilateur pour supprimer les avertissements ou est-ce que l'avertissement n'est plus valide?
Chris Wagner
2
J'ai posté une question de suivi: stackoverflow.com/questions/8859649/…
barfoon
3
Quelqu'un peut-il expliquer pourquoi vous avez besoin des mots-clés __block et __weak? Je suppose qu'un cycle de rétention est en cours de création, mais je ne le vois pas. Et comment la création d'une variable temporaire résout-elle le problème?
user798719
50

Le problème se produit car vous attribuez un bloc à la demande qui contient une référence forte à demander. Le bloc conservera automatiquement la demande, de sorte que la demande d'origine ne sera pas désallouée à cause du cycle. Ça a du sens?

C'est juste bizarre parce que vous marquez l'objet de requête avec __block pour qu'il puisse se référer à lui-même. Vous pouvez résoudre ce problème en créant une référence faible à côté .

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

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:wrequest.rawResponseData error:nil];
    // ...
    }];
ZaBlanc
la source
__weak ASIHTTPRequest * wrequest = demande; N'a pas travaillé pour moi. Donner une erreur J'ai utilisé __block ASIHTTPRequest * blockRequest = request;
Ram G.
13

Cela est dû au maintien de soi dans le bloc. Le bloc sera accédé à partir de soi, et soi est référencé dans le bloc. cela créera un cycle de rétention.

Essayez de résoudre ce problème en créant une référence faible 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];
Développeur HD
la source
C'est la bonne réponse et doit être notée comme telle
Benjamin
6

Parfois, le compilateur xcode a des problèmes pour identifier les cycles de conservation, donc si vous êtes sûr de ne pas conserver le bloc de complétion, vous pouvez mettre un indicateur de compilateur comme celui-ci:

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

-(void)someMethod {
}
GOrozco58
la source
1
Certains pourraient prétendre que c'est une mauvaise conception, mais je crée parfois des objets indépendants qui restent en mémoire jusqu'à ce qu'ils aient terminé avec une tâche asynchrone. Ils sont conservés par une propriété completionBlock qui contient une forte référence à soi, créant un cycle de rétention intentionnel. Le completionBlock contient self.completionBlock = nil, qui libère le completionBlock et interrompt le cycle de conservation, permettant à l'objet d'être libéré de la mémoire une fois la tâche terminée. Votre réponse est utile pour aider à calmer les avertissements qui se produisent lorsque je fais cela.
hyperspasme
1
pour être honnête, les chances que quelqu'un ait raison et que le compilateur se trompe sont très faibles. Donc, je dirais que dépasser les avertissements est une entreprise risquée
Max MacLeod
3

Quand j'essaye la solution fournie par Guillaume, tout va bien en mode Debug mais ça plante en mode Release.

Notez que n'utilisez pas __weak mais __unsafe_unretained car ma cible est iOS 4.3.

Mon code plante lorsque setCompletionBlock: est appelé sur l'objet "request": la requête a été désallouée ...

Donc, cette solution fonctionne à la fois dans les modes Debug et 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];        
}];
bourrasque2022
la source
Solution intéressante. Avez-vous compris pourquoi il plante en mode Release et non 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;
// ....

}];

quelle est la différence entre __weak et __block reference?

Emil Marashliev
la source