Das starke Erfassen des Selbst in diesem Block führt wahrscheinlich zu einem Haltezyklus

207

Wie kann ich diese Warnung in xcode vermeiden? Hier ist das Code-Snippet:

[player(AVPlayer object) addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
queue:nil usingBlock:^(CMTime time) {
    current+=1;

    if(current==60)
    {
        min+=(current/60);
        current = 0;
    }

    [timerDisp(UILabel) setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];///warning occurs in this line
}];
user1845209
quelle
Ist timerDispeine Eigenschaft in der Klasse?
Tim
Ja, @property (nichtatomar, stark) UILabel * timerDisp;
user1845209
2
Was ist das: player(AVPlayer object)und timerDisp(UILabel)?
Carl Veazey
AVPlayer * Player; UILabel * timerDisp;
user1845209
5
Die eigentliche Frage ist, wie Sie diese Warnung ohne eine unnötig schwache Referenz auf sich selbst zum Schweigen bringen können , wenn Sie wissen, dass die Zirkelreferenz unterbrochen wird (z. B. wenn Sie die Referenz immer löschen, wenn eine Netzwerkanforderung abgeschlossen ist).
Glenn Maynard

Antworten:

514

Die Erfassung von selfhier erfolgt mit Ihrem impliziten Eigenschaftszugriff von self.timerDisp- Sie können nicht auf selfoder Eigenschaften selfinnerhalb eines Blocks verweisen, der von stark beibehalten wird self.

Sie können dies umgehen, indem Sie einen schwachen Verweis auf erstellen, selfbevor Sie auf timerDispIhren Block zugreifen :

__weak typeof(self) weakSelf = self;
[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                     queue:nil
                                usingBlock:^(CMTime time) {
                                                current+=1;

                                                if(current==60)
                                                {
                                                    min+=(current/60);
                                                    current = 0;
                                                }

                                                 [weakSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
                                            }];
Tim
quelle
13
Versuchen Sie es __unsafe_unretainedstattdessen.
Tim
63
Aufgelöst. Verwenden Sie stattdessen Folgendes: __unsafe_unretained typeof (self) schwachSelf = self; danke für die Hilfe @Tim
user1845209
1
Gute Antwort, aber ich nehme ein kleines Problem mit Ihnen und sage: "Sie können sich nicht auf sich selbst oder Eigenschaften auf sich selbst innerhalb eines Blocks beziehen, der stark von sich selbst beibehalten wird." Dies ist nicht unbedingt richtig. Bitte sehen Sie meine Antwort unten. Besser zu sagen: "Sie müssen sehr vorsichtig sein, wenn Sie sich auf sich selbst beziehen ..."
Chris Suter
8
Ich sehe keinen Aufbewahrungszyklus im OP-Code. Der Block wird nicht stark von self, sondern von der Hauptversandwarteschlange beibehalten. Liege ich falsch?
Erikprice
3
@erikprice: du liegst nicht falsch. Ich habe die Frage so interpretiert, dass sie sich hauptsächlich auf den Fehler bezieht, den Xcode darstellt ("Wie kann ich diese Warnung in xcode vermeiden?"), Und nicht auf das tatsächliche Vorhandensein eines Aufbewahrungszyklus. Sie sagen zu Recht, dass kein Haltezyklus nur aus dem bereitgestellten Snippet-OP ersichtlich ist.
Tim
52
__weak MyClass *self_ = self; // that's enough
self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
    if (!error) {
       [self_ showAlertWithError:error];
    } else {
       self_.items = [NSArray arrayWithArray:receivedItems];
       [self_.tableView reloadData];
    }
};

Und eine sehr wichtige Sache, die Sie beachten sollten: Verwenden Sie Instanzvariablen nicht direkt im Block, sondern als Eigenschaften eines schwachen Objekts. Beispiel:

self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
        if (!error) {
           [self_ showAlertWithError:error];
        } else {
           self_.items = [NSArray arrayWithArray:receivedItems];
           [_tableView reloadData]; // BAD! IT ALSO WILL BRING YOU TO RETAIN LOOP
        }
 };

und vergessen Sie nicht zu tun:

- (void)dealloc {
    self.loadingCompletionHandler = NULL;
}

Ein weiteres Problem kann auftreten, wenn Sie eine schwache Kopie eines Objekts übergeben, das von niemandem aufbewahrt wird:

MyViewController *vcToGo = [[MyViewCOntroller alloc] init];
__weak MyViewController *vcToGo_ = vcToGo;
self.loadingCompletion = ^{
    [vcToGo_ doSomePrecessing];
};

Wenn vcToGodie Zuordnung aufgehoben und dann dieser Block ausgelöst wird, werden Sie wahrscheinlich mit einem nicht erkannten Selektor in einen Papierkorb stürzen vcToGo_, der jetzt eine Variable enthält . Versuchen Sie es zu kontrollieren.

iiFreeman
quelle
3
Dies wäre eine stärkere Antwort, wenn Sie es auch erklären.
Eric J.
43

Bessere Version

__strong typeof(self) strongSelf = weakSelf;

Erstellen Sie einen starken Verweis auf diese schwache Version als erste Zeile in Ihrem Block. Wenn self noch vorhanden ist, wenn der Block ausgeführt wird, und nicht auf Null zurückgefallen ist, stellt diese Zeile sicher, dass es während der gesamten Ausführungslebensdauer des Blocks bestehen bleibt.

Das Ganze wäre also so:

// Establish the weak self reference
__weak typeof(self) weakSelf = self;

[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                 queue:nil
                            usingBlock:^(CMTime time) {

    // Establish the strong self reference
    __strong typeof(self) strongSelf = weakSelf;

    if (strongSelf) {
        [strongSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
    } else {
        // self doesn't exist
    }
}];

Ich habe diesen Artikel oft gelesen. Dies ist ein ausgezeichneter Artikel von Erica Sadun über das Vermeiden von Problemen bei der Verwendung von Blöcken und NSNotificationCenter


Schnelles Update:

Zum Beispiel wäre in kurzer Zeit eine einfache Methode mit Erfolgsblock:

func doSomeThingWithSuccessBlock(success: () -> ()) {
    success()
}

Wenn wir diese Methode aufrufen und selfim Erfolgsblock verwenden müssen. Wir werden die [weak self]und guard letFunktionen verwenden.

    doSomeThingWithSuccessBlock { [weak self] () -> () in
        guard let strongSelf = self else { return }
        strongSelf.gridCollectionView.reloadData()
    }

Dieser sogenannte stark-schwache Tanz wird von beliebten Open-Source-Projekten verwendet Alamofire.

Weitere Informationen finden Sie im Swift-Style-Guide

Warif Akhand Rishi
quelle
Was ist, wenn Sie typeof(self) strongSelf = self;außerhalb des Blocks (anstelle von __schwach) getan haben, dann in dem Block strongSelf = nil;nach der Verwendung gesagt ? Ich verstehe nicht, wie Ihr Beispiel sicherstellt, dass schwaches Selbst zum Zeitpunkt der Ausführung des Blocks nicht gleich Null ist.
Matt
Um mögliche Aufbewahrungszyklen zu vermeiden, erstellen wir eine schwache Selbstreferenz außerhalb eines Blocks, der self in seinem Code verwendet. Auf Ihre Weise müssen Sie sicherstellen, dass der Block ausgeführt wird. Ein weiterer Block Ihres Codes ist jetzt dafür verantwortlich, Ihren zuvor gespeicherten Speicher freizugeben.
Warif Akhand Rishi
@Matt Der Zweck dieses Beispiels besteht nicht darin, das schwache Selbst beizubehalten. Der Zweck ist, wenn das schwache Selbst nicht Null ist, eine starke Referenz innerhalb des Blocks zu machen. Sobald der Block mit self ausgeführt wird, wird self innerhalb des Blocks nicht null.
Warif Akhand Rishi
15

In einer anderen Antwort sagte Tim:

Sie können nicht innerhalb eines Blocks, der stark von self beibehalten wird, auf self oder Eigenschaften auf self verweisen.

Das ist nicht ganz richtig. Es ist in Ordnung, dies zu tun, solange Sie den Zyklus irgendwann unterbrechen. Nehmen wir zum Beispiel an, Sie haben einen Timer, der ausgelöst wird und einen Block hat, der sich selbst behält, und Sie behalten auch einen starken Bezug zum Timer in sich. Dies ist vollkommen in Ordnung, wenn Sie immer wissen, dass Sie den Timer irgendwann zerstören und den Zyklus unterbrechen werden.

In meinem Fall hatte ich gerade diese Warnung für Code, der Folgendes tat:

[x setY:^{ [x doSomething]; }];

Jetzt weiß ich zufällig, dass clang diese Warnung nur dann ausgibt, wenn festgestellt wird, dass die Methode mit „set“ beginnt (und einem weiteren Sonderfall, den ich hier nicht erwähne). Ich weiß, dass keine Gefahr besteht, dass es eine Retain-Schleife gibt. Deshalb habe ich den Methodennamen in „useY:“ geändert. Das ist natürlich möglicherweise nicht in allen Fällen angemessen, und normalerweise möchten Sie eine schwache Referenz verwenden, aber Ich fand es wert, meine Lösung zur Kenntnis zu nehmen, falls sie anderen hilft.

Chris Suter
quelle
4

Oft ist dies kein Aufbewahrungszyklus .

Wenn Sie wissen, dass dies nicht der Fall ist, müssen Sie keine fruchtlosen Schwachen in die Welt bringen.

Apple zwingt uns diese Warnungen sogar mit der API auf ihre UIPageViewController, die eine festgelegte Methode (die diese Warnungen auslöst - wie an anderer Stelle erwähnt - mit dem Gedanken, dass Sie einen Wert auf einen ivar setzen, der ein Block ist) und einen Vervollständigungs-Handler-Block (in dem) enthalten Sie werden sich zweifellos auf sich selbst beziehen).

Hier sind einige Compiler-Anweisungen, um die Warnung aus dieser einen Codezeile zu entfernen:

#pragma GCC diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
    [self.pageViewController setViewControllers:@[newViewController] direction:navigationDirection animated:YES completion:^(BOOL finished) {
        // this warning is caused because "setViewControllers" starts with "set…", it's not a problem
        [self doTheThingsIGottaDo:finished touchThePuppetHead:YES];
    }];
#pragma GCC diagnostic pop
bshirley
quelle
1

Hinzufügen von zwei Cent zur Verbesserung von Präzision und Stil. In den meisten Fällen verwenden Sie selfin diesem Block nur ein oder mehrere Mitglieder von , wahrscheinlich nur, um einen Schieberegler zu aktualisieren. Casting selfist übertrieben. Stattdessen ist es besser, explizit zu sein und nur die Objekte zu werfen , die Sie wirklich benötigen, innerhalb des Blocks. Zum Beispiel, wenn es eine Instanz ist UISlider*, sagen wir, _timeSlidertun gerade folgendes vor dem Block Erklärung:

UISlider* __weak slider = _timeSlider;

Dann einfach sliderinnerhalb des Blocks verwenden. Technisch gesehen ist dies genauer, da der potenzielle Aufbewahrungszyklus nur auf das Objekt beschränkt wird, das Sie benötigen, nicht auf alle Objekte im Inneren self.

Vollständiges Beispiel:

UISlider* __weak slider = _timeSlider;
[_embeddedPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)
     queue:nil
     usingBlock:^(CMTime time){
        slider.value = time.value/time.timescale;
     }
];

Darüber hinaus ist das Objekt, das in einen schwachen Zeiger umgewandelt wird, höchstwahrscheinlich bereits ein schwacher Zeiger im Inneren, selfwodurch die Wahrscheinlichkeit eines Aufbewahrungszyklus minimiert oder vollständig beseitigt wird. Im obigen Beispiel _timeSliderist tatsächlich eine Eigenschaft als schwache Referenz gespeichert, z.

@property (nonatomic, weak) IBOutlet UISlider* timeSlider;

In Bezug auf den Codierungsstil werden Variablendeklarationen wie bei C und C ++ besser von rechts nach links gelesen. Die Erklärung SomeType* __weak variablein dieser Reihenfolge lautet natürlicher von rechts nach links als : variable is a weak pointer to SomeType.

Luis Artola
quelle
1

Ich bin kürzlich auf diese Warnung gestoßen und wollte sie etwas besser verstehen. Nach einigem Ausprobieren stellte ich fest, dass eine Methode entweder mit "Hinzufügen" oder "Speichern" beginnt. Ziel C behandelt Methodennamen, die mit "neu", "zuordnen" usw. beginnen, als Rückgabe eines beibehaltenen Objekts, erwähnt jedoch nichts (was ich finden kann) über "Hinzufügen" oder "Speichern". Wenn ich jedoch einen Methodennamen auf diese Weise verwende:

[self addItemWithCompletionBlock:^(NSError *error) {
            [self done]; }];

Ich werde die Warnung in der Zeile [selbst erledigt] sehen. Dies wird jedoch nicht:

[self itemWithCompletionBlock:^(NSError *error) {
    [self done]; }];

Ich werde fortfahren und die Methode "__weak __typeof (self) schwachSelf = self" verwenden, um auf mein Objekt zu verweisen, aber ich mag es wirklich nicht, dies zu tun, da dies ein zukünftiges Ich und / oder einen anderen Entwickler verwirren wird. Natürlich könnte ich auch nicht "add" (oder "save") verwenden, aber das ist schlimmer, da es die Bedeutung der Methode wegnimmt.

Ray M.
quelle