Korrektur der Warnung "Das starke Erfassen von [einem Objekt] in diesem Block führt wahrscheinlich zu einem Aufbewahrungszyklus" im ARC-fähigen Code

141

Wie kann in ARC-fähigem Code eine Warnung über einen möglichen Aufbewahrungszyklus behoben werden, wenn eine blockbasierte API verwendet wird?

Die Warnung:
Capturing 'request' strongly in this block is likely to lead to a retain cycle

produziert von diesem Codeausschnitt:

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

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

Die Warnung hängt mit der Verwendung des Objekts requestinnerhalb des Blocks zusammen.

Guillaume
quelle
1
Sie sollten wahrscheinlich responseDatastattdessen rawResponseDatadie ASIHTTPRequest-Dokumentation verwenden.
0xced

Antworten:

165

Ich antworte mir:

Mein Verständnis der Dokumentation besagt, dass die Verwendung des Schlüsselworts blockund das Setzen der Variablen auf Null nach der Verwendung innerhalb des Blocks in Ordnung sein sollte, aber die Warnung weiterhin angezeigt wird.

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

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

    }];

Update: Es funktioniert mit dem Schlüsselwort '_ schwach' anstelle von ' _block' und unter Verwendung einer temporären Variablen:

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

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

Wenn Sie auch auf iOS 4 abzielen möchten, verwenden Sie __unsafe_unretainedanstelle von __weak. Gleiches Verhalten, aber der Zeiger bleibt baumeln, anstatt automatisch auf Null gesetzt zu werden, wenn das Objekt zerstört wird.

Guillaume
quelle
8
Basierend auf den ARC-Dokumenten klingt es so, als müssten Sie __unsafe_unretained __block zusammen verwenden, um das gleiche Verhalten wie zuvor zu erzielen, wenn Sie ARC und Blöcke verwenden.
Hunter
4
@ SeanClarkHess: Wenn ich die ersten beiden Zeilen kombiniere, erhalte ich folgende Warnung: "Zuweisen eines beibehaltenen Objekts zu einer schwachen Variablen; Objekt wird nach der Zuweisung freigegeben"
Guillaume
1
@ Guillaume danke für die Antwort, einige wie ich die temporäre Variable übersehen habe, habe das versucht und die Warnungen sind weg. Wissen Sie, warum das funktioniert? Betrügt es nur den Compiler, die Warnungen zu unterdrücken, oder ist die Warnung tatsächlich nicht mehr gültig?
Chris Wagner
2
Ich habe eine Folgefrage gepostet: stackoverflow.com/questions/8859649/…
Barfoon
3
Kann jemand erklären, warum Sie die Schlüsselwörter __block und __weak benötigen? Ich denke, es wird ein Aufbewahrungszyklus erstellt, aber ich sehe ihn nicht. Und wie behebt das Erstellen einer temporären Variablen das Problem?
user798719
50

Das Problem tritt auf, weil Sie der Anforderung einen Block zuweisen, der einen starken Verweis auf die Anforderung enthält. Der Block behält die Anforderung automatisch bei, sodass die ursprüngliche Anforderung aufgrund des Zyklus nicht freigegeben wird. Sinn ergeben?

Es ist nur seltsam, weil Sie das Anforderungsobjekt mit __block markieren, damit es auf sich selbst verweisen kann. Sie können dies beheben, indem Sie eine schwache Referenz daneben erstellen .

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

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:wrequest.rawResponseData error:nil];
    // ...
    }];
ZaBlanc
quelle
__schwache ASIHTTPRequest * wrequest = Anfrage; hat bei mir nicht funktioniert. Fehler geben Ich habe __block ASIHTTPRequest * blockRequest = request verwendet.
Ram G.
13

Es verursacht aufgrund des Haltens des Selbst im Block. Auf den Block wird von self aus zugegriffen, und auf self wird im Block verwiesen. Dadurch wird ein Aufbewahrungszyklus erstellt.

Versuchen Sie, dies zu lösen, indem Sie eine schwache Referenz von erstellen 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];
HD-Entwickler
quelle
Dies ist die richtige Antwort und sollte als solche vermerkt werden
Benjamin
6

Manchmal hat der xcode-Compiler Probleme bei der Identifizierung der Aufbewahrungszyklen. Wenn Sie also sicher sind, dass Sie den CompletionBlock nicht beibehalten, können Sie ein Compiler-Flag wie folgt setzen:

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

-(void)someMethod {
}
GOrozco58
quelle
1
Einige mögen argumentieren, dass es schlechtes Design ist, aber ich erstelle manchmal unabhängige Objekte, die im Speicher hängen bleiben, bis sie mit einer asynchronen Aufgabe fertig sind. Sie werden von einer CompletionBlock-Eigenschaft beibehalten, die einen starken Verweis auf sich selbst enthält und einen absichtlichen Aufbewahrungszyklus erzeugt. Der CompletionBlock enthält self.completionBlock = nil, wodurch der CompletionBlock freigegeben und der Aufbewahrungszyklus unterbrochen wird, sodass das Objekt nach Abschluss der Aufgabe aus dem Speicher freigegeben werden kann. Ihre Antwort ist hilfreich, um die Warnungen zu beruhigen, die dabei auftreten.
Hyperspasmus
1
Um ehrlich zu sein, sind die Chancen sehr gering, dass einer Recht hat und der Compiler Unrecht hat. Ich würde also sagen, nur die Warnungen zu übertreffen, ist ein riskantes Geschäft
Max MacLeod
3

Wenn ich die von Guillaume bereitgestellte Lösung ausprobiere, ist im Debug-Modus alles in Ordnung, im Release-Modus stürzt sie jedoch ab.

Beachten Sie, dass Sie nicht __weak, sondern __unsafe_unretained verwenden, da mein Ziel iOS 4.3 ist.

Mein Code stürzt ab, wenn setCompletionBlock: für das Objekt "request" aufgerufen wird: request wurde freigegeben ...

Diese Lösung funktioniert also sowohl im Debug- als auch im Release-Modus:

// 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
quelle
Interessante Lösung. Haben Sie herausgefunden, warum es im Release-Modus und nicht im Debug abstürzt?
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;
// ....

}];

Was ist der Unterschied zwischen __schwacher und __blocker Referenz?

Emil Marashliev
quelle