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.