Kurze Antwort
Anstatt self
direkt darauf zuzugreifen , sollten Sie indirekt über eine Referenz darauf zugreifen, die nicht beibehalten wird. Wenn Sie die automatische Referenzzählung (ARC) nicht verwenden , können Sie Folgendes tun:
__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Das __block
Schlüsselwort markiert Variablen, die innerhalb des Blocks geändert werden können (das tun wir nicht), aber sie werden auch nicht automatisch beibehalten, wenn der Block beibehalten wird (es sei denn, Sie verwenden ARC). Wenn Sie dies tun, müssen Sie sicher sein, dass nichts anderes versucht, den Block auszuführen, nachdem die MyDataProcessor-Instanz freigegeben wurde. (Angesichts der Struktur Ihres Codes sollte dies kein Problem sein.) Lesen Sie mehr darüber__block
.
Wenn Sie ARC verwenden , __block
bleiben die Semantik der Änderungen und die Referenz erhalten. In diesem Fall sollten Sie sie deklarieren__weak
stattdessen .
Lange Antwort
Angenommen, Sie hatten folgenden Code:
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
Das Problem hierbei ist, dass self einen Verweis auf den Block behält; In der Zwischenzeit muss der Block einen Verweis auf self behalten, um seine Delegate-Eigenschaft abzurufen und dem Delegate eine Methode zu senden. Wenn alles andere in Ihrer App den Verweis auf dieses Objekt freigibt, ist die Anzahl der Beibehaltungen nicht Null (weil der Block darauf zeigt) und der Block macht nichts falsch (weil das Objekt darauf zeigt) und so weiter Das Objektpaar wird in den Heap gelangen und Speicher belegen, ist jedoch ohne Debugger für immer nicht erreichbar. Wirklich tragisch.
Dieser Fall könnte einfach dadurch behoben werden:
id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
[progressDelegate processingWithProgress:percentComplete];
}
In diesem Code behält self den Block bei, der Block behält den Delegaten und es gibt keine Zyklen (von hier aus sichtbar; der Delegat behält möglicherweise unser Objekt, aber das liegt momentan nicht in unserer Hand). Dieser Code riskiert kein Leck auf die gleiche Weise, da der Wert der Delegate-Eigenschaft beim Erstellen des Blocks erfasst wird, anstatt bei der Ausführung nachgeschlagen zu werden. Ein Nebeneffekt ist, dass der Block beim Ändern des Delegaten nach dem Erstellen dieses Blocks weiterhin Aktualisierungsnachrichten an den alten Delegaten sendet. Ob dies wahrscheinlich ist oder nicht, hängt von Ihrer Anwendung ab.
Selbst wenn Sie mit diesem Verhalten cool waren, können Sie diesen Trick in Ihrem Fall immer noch nicht anwenden:
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
Hier übergeben Sie self
im Methodenaufruf direkt an den Delegaten, sodass Sie ihn irgendwo dort abrufen müssen. Wenn Sie die Definition des Blocktyps steuern können, ist es am besten, den Delegaten als Parameter an den Block zu übergeben:
self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};
Diese Lösung vermeidet den Aufbewahrungszyklus und ruft immer den aktuellen Delegaten an.
Wenn Sie den Block nicht ändern können, können Sie damit umgehen . Der Grund, warum ein Aufbewahrungszyklus eine Warnung und kein Fehler ist, ist, dass sie nicht unbedingt das Schicksal Ihrer Anwendung bedeuten. Wenn MyDataProcessor
die Blöcke nach Abschluss des Vorgangs freigegeben werden können, bevor der übergeordnete Vorgang versucht, sie freizugeben, wird der Zyklus unterbrochen und alles wird ordnungsgemäß bereinigt. Wenn Sie sich dessen sicher sein könnten, wäre es das Richtige, a zu verwenden #pragma
, um die Warnungen für diesen Codeblock zu unterdrücken. (Oder verwenden Sie ein Compiler-Flag pro Datei. Deaktivieren Sie die Warnung jedoch nicht für das gesamte Projekt.)
Sie können auch einen ähnlichen Trick wie oben verwenden, indem Sie eine Referenz als schwach oder nicht beibehalten deklarieren und diese im Block verwenden. Beispielsweise:
__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Alle drei oben genannten Punkte geben Ihnen eine Referenz, ohne das Ergebnis beizubehalten, obwohl sie sich alle etwas anders verhalten: __weak
Versuchen Sie, die Referenz auf Null zu setzen, wenn das Objekt freigegeben wird. __unsafe_unretained
wird Sie mit einem ungültigen Zeiger verlassen; __block
fügt tatsächlich eine weitere Indirektionsebene hinzu und ermöglicht es Ihnen, den Wert der Referenz innerhalb des Blocks zu ändern (in diesem Fall irrelevant, dadp
er nirgendwo anders verwendet wird).
Was am besten ist, hängt davon ab, welchen Code Sie ändern können und welchen nicht. Aber hoffentlich hat Ihnen dies einige Ideen gegeben, wie Sie vorgehen sollen.
dp
die Freigabe freigegeben wird (z. B. wenn es sich um einen Ansichtscontroller handelte und dieser eingeblendet wurde), die Zeile[dp.delegate ...
EXC_BADACCESS verursacht?strong
oderweak
?@weakify(..)
und@strongify(...)
die es Ihnen ermöglichen,self
in Block ohne Beibehaltung zu verwenden.Es gibt auch die Möglichkeit, die Warnung zu unterdrücken, wenn Sie sicher sind, dass der Zyklus in Zukunft unterbrochen wird:
Auf diese Weise können zum Affen nicht herum haben mit
__weak
,self
Aliasing und explizite Ivar Vorfixierung.quelle
__weak id weakSelf = self;
das grundlegend anders verhält als das Unterdrücken der Warnung. Die Frage begann mit "... wenn Sie sicher sind, dass der Aufbewahrungszyklus unterbrochen wird"[array addObject:weakObject];
Wenn das schwache Objekt freigegeben wurde, verursacht dies einen Absturz. Dies ist natürlich einem Haltezyklus nicht vorzuziehen. Sie müssen verstehen, ob Ihr Block tatsächlich lange genug lebt, um eine Schwächung zu rechtfertigen, und ob die Aktion im Block davon abhängen soll, ob das schwache Objekt noch gültig ist.Für eine allgemeine Lösung habe ich diese im vorkompilierten Header definiert. Vermeidet das Erfassen und aktiviert dennoch die Compiler-Hilfe, indem die Verwendung vermieden wird
id
Dann können Sie im Code Folgendes tun:
quelle
self
in Ihrem Block @weakify (self) fortsetzen können. id block = ^ {@strongify (self); [self.delegate myAPIDidFinish: self]; };Ich glaube, die Lösung ohne ARC funktioniert auch mit ARC unter Verwendung des
__block
Schlüsselworts:BEARBEITEN: Gemäß den Versionshinweisen zur Umstellung auf ARC
__block
bleibt ein mit Speicher deklariertes Objekt weiterhin erhalten. Verwenden Sie__weak
(bevorzugt) oder__unsafe_unretained
(aus Gründen der Abwärtskompatibilität).quelle
__block
Schlüsselwort es vermieden hat, seinen Referenten beizubehalten. Vielen Dank! Ich habe meine monolithische Antwort aktualisiert. :-)Wenn ich ein paar andere Antworten kombiniere, verwende ich dies jetzt für ein typisiertes schwaches Selbst, um es in Blöcken zu verwenden:
Ich habe das als XCode-Code-Snippet mit dem Abschlusspräfix "welf" in Methoden / Funktionen festgelegt, das nur nach Eingabe von "we" angezeigt wird.
quelle
warning => "Das Erfassen des Selbst innerhalb des Blocks führt wahrscheinlich zu einem Haltezyklus."
Wenn Sie sich selbst oder seine Eigenschaft innerhalb eines Blocks referenzieren, der von sich selbst stark beibehalten wird, als es die obige Warnung zeigt.
Um dies zu vermeiden, müssen wir eine Woche ref
also anstatt zu benutzen
Wir sollten ... benutzen
Hinweis: Der Beibehaltungszyklus tritt normalerweise auf, wenn einige Objekte, die sich aufeinander beziehen und bei denen beide den Referenzzähler = 1 haben und deren Delloc-Methode nie aufgerufen wird.
quelle
Der neue Weg, dies zu tun, ist die Verwendung von @weakify und @strongify marco
Weitere Informationen zu @Weakify @Strongify Marco
quelle
Wenn Sie sicher sind, dass Ihr Code keinen Aufbewahrungszyklus erstellt oder dass der Zyklus später unterbrochen wird, können Sie die Warnung am einfachsten zum Schweigen bringen:
Der Grund dafür ist, dass der Punktzugriff auf Eigenschaften bei der Xcode-Analyse berücksichtigt wird und daher
Wenn Methodenaufrufe durch x von y (auf der linken Seite der Zuweisung) und durch y von x (auf der rechten Seite) beibehalten werden, werden Methodenaufrufe nicht derselben Analyse unterzogen, selbst wenn es sich um Methodenaufrufe mit Eigenschaftszugriff handelt Dies entspricht dem Punktzugriff, auch wenn diese Eigenschaftszugriffsmethoden vom Compiler generiert wurden
Nur auf der rechten Seite wird eine Aufbewahrung erstellt (um y von x), und es wird keine Warnung zum Aufbewahrungszyklus generiert.
quelle