6

Ich habe schon seit einiger Zeit Blöcke verwendet, aber ich denke, dass es Dinge gibt, die ich vermisse Speicherverwaltung in ARC und nicht-ARC-Umgebungen. Ich glaube, dass ein tieferes Verständnis mich dazu bringen wird, viele Speicherlecks aufzuheben.Objective C Speicherverwaltung mit Blöcken, ARC und nicht ARC

AFNetworking ist meine Hauptanwendung von Blöcken in einer bestimmten Anwendung. Meistens mache ich in einem Completion-Handler einer Operation etwas wie "[self.myArray addObject]".

Sowohl in ARC- als auch in nicht ARC-fähigen Umgebungen wird "self" gemäß this article from Apple beibehalten.

Das bedeutet, dass immer, wenn ein Completion-Block einer AFNetworking-Netzwerkoperation aufgerufen wird, self in diesem Block beibehalten und freigegeben wird, wenn dieser Block den Gültigkeitsbereich verlässt. Ich glaube, dass dies sowohl für ARC als auch für Nicht-ARC gilt. Ich habe sowohl das Leaks-Tool als auch den Static Analyzer ausgeführt, um Speicherlecks zu finden. Keine zeigte irgendwelche.

Allerdings war ich erst vor kurzem auf eine Warnung gestoßen, die ich nicht herausfinden konnte. Ich verwende ARC in diesem speziellen Beispiel.

ich zwei Instanzvariablen, die den Abschluss und Ausfall eines Netzbetriebs zeigen

@property (nonatomic, readwrite, copy) SFCompletionBlock completionBlock; 
@property (nonatomic, readwrite, copy) SFFailureBlock failureBlock; 
@synthesize failureBlock = _failureBlock; 
@synthesize operation = _operation; 

Irgendwo im Code, mache ich das:

[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id 
                responseObject) { 
NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}]; 
      _failureBlock(error); 
     } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 
      NSLog(@"nothing"); 
     }]; 

Xcode beschwert sich über die Linie, die die Anrufe failureBlock, mit der Meldung "Capturing" self "stark in diesem Block führt wahrscheinlich zu einem Retain-Zyklus. Ich glaube, Xcode ist richtig: Der Fehler Block behält Selbst, und Selbst hält seine eigene Kopie des Blocks, so keiner der beiden wird freigegeben

Allerdings habe ich folgende Fragen/Beobachtungen.

1) Wenn ich _failureBlock (Fehler) in "self.failureBlock (Fehler)" (ohne Anführungszeichen) ändern, hört der Compiler auf sich zu beschweren. Warum das? Ist das ein Speicherleck, das der Compiler vermisst?

2) Was ist die beste Vorgehensweise beim Arbeiten mit Blöcken in ARC- und nicht ARC-fähigen Umgebungen, wenn Blöcke verwendet werden, die Instanzvariablen sind? Scheint, dass im Fall von Abschluss- und Fehlerblöcken in AFNetworking diese zwei Blöcke keine Instanzvariablen sind, so dass sie wahrscheinlich nicht in die Kategorie der Retain-Zyklen fallen, die ich oben beschrieben habe. Aber wenn Fortschrittsblöcke in AFNetworking verwendet werden, was kann getan werden, um Retain-Zyklen wie den obigen zu vermeiden?

Ich würde gerne die Gedanken anderer Leute über ARC und Nicht-ARC mit Blöcken und Problemen/Lösungen mit Speicherverwaltung hören. Ich finde diese Situationen fehleranfällig und ich denke, dass einige Diskussionen darüber notwendig sind, um die Dinge zu klären.

Ich weiß nicht, ob es darauf ankommt, aber ich verwende Xcode 4.4 mit dem neuesten LLVM.

Antwort

6

1) Wenn i _failureBlock ändern (Fehler) auf "self.failureBlock (Fehler)" (ohne Anführungszeichen) der Compiler stoppt beschweren. Warum das? Ist das ein Speicherverlust der Compiler verfehlt?

Der Retain-Zyklus existiert in beiden Fällen. Wenn Sie iOS 5 + Targeting sind, können Sie in einem schwachen Verweis auf Selbst passieren:

__weak MyClass *weakSelf; 
[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { 
    NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}]; 
    if (weakSelf.failureBlock) weakSelf.failureBlock(error); 
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { 
    NSLog(@"nothing"); 
}]; 

Jetzt wird selbst nicht beibehalten werden, und wenn es vor der Rückruf freigegeben ist aufgerufen wird, ist der Rückruf eine no- op. Allerdings ist es möglich, dass es Deallokation wird unterziehen kann, während der Rückruf auf einem Hintergrund-Thread aufgerufen wird, so dass dieses Muster gelegentliche Abstürze erstellen kann.

2) In der Regel, was ist die beste Praxis sowohl mit Blöcken in ARC zu arbeiten und nicht-ARC fähige Umgebungen, wenn Blöcke verwenden, die Instanzvariablen sind? Es scheint, dass diese beiden Blöcke nicht Instanzvariablen bei der Fertigstellung und Versagen Blöcke in AFNetworking, so sie wahrscheinlich nicht in die Kategorie der Zyklen behalten fallen, die ich oben beschrieben. Aber wenn Fortschritte Blöcke in AFNetworking Verwendung was kann getan werden, um zu vermeiden Zyklen wie die behalten oben?

Die meiste Zeit denke ich it's better not to store blocks in instance variables. Wenn Sie stattdessen den Block von einer Methode in Ihrer Klasse zurückkehren, werden Sie noch ein Zyklus beibehalten, aber es existiert erst ab dem Zeitpunkt der Methode zu dem Zeitpunkt aufgerufen wird, der Block freigegeben wird. So wird verhindert, dass Ihre Instanz während der Blockausführung freigegeben wird, aber der Aufbewahrungszyklus endet, wenn der Block freigegeben wird:

+0

Vielen Dank für diese Antwort. Das ist eine Menge Denkanstöße. Nebenbei bemerkt ist das, was Sie über den Callback und den Hintergrund-Thread gesagt haben, der Abstürze verursacht, korrekt. In meinem Fall werden jedoch alle Completion-Blöcke im Hauptthread aufgerufen, um solche Probleme zu vermeiden.Wie bei der anderen Sache, die Lösung "den Block von einer Methode zurückgeben", um Zyklen zu behalten, löst dies nicht das grundlegende Problem des Erstellens einer wiederverwendbaren Komponente, bei der der Aufrufer den Abschlussblock entscheidet. – csotiriou

+0

Wenn Sie eine API mit Blockrückrufen verfügbar machen, können Sie nicht verhindern, dass Ihre Aufrufer eigene Aufbewahrungszyklen erstellen. Ihre API sollte darauf achten, die Callback-Blöcke so schnell wie möglich zu veröffentlichen, und sollte wahrscheinlich eine Abbruchmethode verfügbar machen, die auch die Callback-Blöcke freigibt. In der Praxis implementieren die meisten Anrufer die Callbacks inline und nicht in den Eigenschaften. In diesem Fall entspricht die Speichersemantik dem Zurückgeben des Blocks aus einer Methode. Der Aufbewahrungszyklus beginnt beim Aufruf der API und endet, wenn die API den Rückrufblock freigibt. –

2

Das bedeutet, dass immer dann, wenn ein Beendigungsblock eines AFNetworking Netzwerk Operation genannt wird, selbst innerhalb dieses Blocks gehalten wird, und freigesetzt, wenn dieser Block den Bereich verlässt.

Nein, self wird vom Block beibehalten, wenn der Block erstellt wird.Und es wird freigegeben, wenn der Block freigegeben wird.

Ich glaube, Xcode ist richtig: der Ausfall Block selbst behält und selbst hält seine eigene Kopie des Blocks, so dass keiner der beiden wird ausgeplant werden.

Der Block in Frage, die self behält der zu setCompletionBlockWithSuccess geleitet Abschluss Block. self enthält keinen Verweis auf diesen Block. Vielmehr self.operation (vermutlich eine Art NSOperation) behält die Blöcke, während es ausgeführt wird. Es gibt also vorübergehend einen Zyklus. Wenn die Operation jedoch ausgeführt wird, wird der Zyklus unterbrochen.

1) Wenn i _failureBlock ändern (Fehler) auf "self.failureBlock (Fehler)" (ohne Anführungszeichen) der Compiler stoppt beschweren. Warum das? Ist das ein Speicherverlust der Compiler verfehlt?

Es sollte keinen Unterschied geben. self wird in beiden Fällen erfasst. Es ist nicht garantiert, dass der Compiler alle Fälle von Aufbewahrungszyklen abfängt.