se capturer fortement dans ce bloc est susceptible d'entraîner un cycle de rétention

207

Comment puis-je éviter cet avertissement dans xcode. Voici l'extrait de code:

[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
la source
Une timerDisppropriété est-elle sur la classe?
Tim
Oui, @property (nonatomique, fort) UILabel * timerDisp;
user1845209
2
Qu'est-ce que c'est: player(AVPlayer object)et timerDisp(UILabel)?
Carl Veazey
Lecteur AVPlayer *; UILabel * timerDisp;
user1845209
5
La vraie question est de savoir comment faire taire cet avertissement sans référence faible inutile sur soi, lorsque vous savez que la référence circulaire sera rompue (par exemple, si vous effacez toujours la référence à la fin d'une demande réseau).
Glenn Maynard

Réponses:

514

La capture d' selfici arrive avec votre accès implicite à la propriété de self.timerDisp- vous ne pouvez pas faire référence à selfou à des propriétés selfdans un bloc qui sera fortement conservé par self.

Vous pouvez contourner cela en créant une référence faible selfavant d'accéder à l' timerDispintérieur de votre bloc:

__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
la source
13
Essayez d'utiliser à la __unsafe_unretainedplace.
Tim
63
Résolu. utilisez ceci à la place: __unsafe_unretained typeof (self) faibleSelf = self; merci pour l'aide @Tim
user1845209
1
Bonne réponse, mais je prends un petit problème avec vous en disant: "vous ne pouvez pas faire référence à soi ou à des propriétés sur soi à l'intérieur d'un bloc qui sera fortement conservé par soi." Ce n'est pas strictement vrai. Veuillez voir ma réponse ci-dessous. Mieux vaut dire: «vous devez faire très attention si vous vous référez à vous-même…»
Chris Suter
8
Je ne vois pas de cycle de conservation dans le code OP. Le bloc n'est pas fortement conservé par self, il est conservé par la file d'attente principale de répartition. Ai-je tort?
erikprice
3
@erikprice: vous ne vous trompez pas. J'ai interprété la question comme étant principalement sur l'erreur que Xcode présente ("Comment puis-je éviter cet avertissement dans xcode"), plutôt que sur la présence réelle d'un cycle de rétention. Vous avez raison de dire qu'aucun cycle de rétention n'est évident uniquement à partir de l'extrait OP fourni.
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];
    }
};

Et une chose très importante à retenir: n'utilisez pas de variables d'instance directement dans le bloc, utilisez-les comme propriétés d'un objet faible, échantillon:

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
        }
 };

et n'oubliez pas de faire:

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

un autre problème peut apparaître si vous passez une copie faible de l'objet non conservé par quelqu'un:

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

Si vcToGosera désalloué et que ce bloc sera déclenché, je pense que vous obtiendrez un crash avec un sélecteur non reconnu dans une corbeille qui contient vcToGo_maintenant une variable. Essayez de le contrôler.

iiFreeman
la source
3
Ce serait une réponse plus forte si vous l'expliquez également.
Eric
43

Meilleure version

__strong typeof(self) strongSelf = weakSelf;

Créez une référence forte à cette version faible comme première ligne de votre bloc. Si self existe toujours lorsque le bloc commence à s'exécuter et n'est pas revenu à zéro, cette ligne garantit qu'il persiste pendant toute la durée d'exécution du bloc.

Donc, le tout serait comme ça:

// 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
    }
}];

J'ai lu cet article plusieurs fois. Ceci est un excellent article par Erica Sadun sur la façon d'éviter les problèmes lors de l'utilisation de blocs et de NSNotificationCenter


Mise à jour rapide:

Par exemple, dans swift, une méthode simple avec bloc de réussite serait:

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

Lorsque nous appelons cette méthode et devons l'utiliser selfdans le bloc de réussite. Nous utiliserons les fonctionnalités [weak self]et guard let.

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

Cette danse dite forte-faible est utilisée par des projets open source populaires Alamofire.

Pour plus d'informations, consultez le guide de style rapide

Warif Akhand Rishi
la source
Que faire si vous avez fait en typeof(self) strongSelf = self;dehors du bloc (au lieu de __weak) puis dans le bloc dit strongSelf = nil;après utilisation? Je ne vois pas comment votre exemple garantit que faiblesseSelf n'est pas nul au moment où le bloc s'exécute.
Matt
Pour éviter d'éventuels cycles de rétention, nous établissons une auto-référence faible en dehors de tout bloc qui utilise self dans son code. À votre façon, vous devez vous assurer que le blocage est exécuté. Un autre bloc de code ur est maintenant responsable de la libération de la mémoire précédemment conservée.
Warif Akhand Rishi
@Matt le but de cet exemple n'est pas de conserver le faibleSelf. Le but est, si le faible soi n'est pas nul, de faire une référence forte à l'intérieur du bloc. Ainsi, une fois que le bloc commence à s'exécuter avec self, self ne devient pas nul à l'intérieur du bloc.
Warif Akhand Rishi
15

Dans une autre réponse, Tim a déclaré:

vous ne pouvez pas faire référence à soi ou à des propriétés sur soi à partir d'un bloc qui sera fortement conservé par lui-même.

Ce n'est pas tout à fait vrai. Vous pouvez le faire tant que vous rompez le cycle à un moment donné. Par exemple, disons que vous avez une minuterie qui se déclenche qui a un bloc qui se conserve et que vous gardez également une référence forte à la minuterie en soi. C'est parfaitement bien si vous savez toujours que vous allez détruire la minuterie à un moment donné et briser le cycle.

Dans mon cas tout à l'heure, j'ai eu cet avertissement pour le code qui:

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

Maintenant, je sais que clang ne produira cet avertissement que s'il détecte que la méthode commence par «set» (et un autre cas spécial que je ne mentionnerai pas ici). Pour moi, je sais qu'il n'y a aucun danger qu'il y ait une boucle de retenue, j'ai donc changé le nom de la méthode en «useY:» Bien sûr, cela pourrait ne pas être approprié dans tous les cas et généralement vous voudrez utiliser une référence faible, mais J'ai pensé qu'il valait la peine de noter ma solution au cas où cela aiderait les autres.

Chris Suter
la source
4

Plusieurs fois, ce n'est pas réellement un cycle de conservation .

Si vous savez que ce n'est pas le cas, vous n'avez pas besoin de mettre au monde des faibles faibles.

Apple nous impose même ces avertissements avec l'API UIPageViewController, ce qui inclut une méthode définie (qui déclenche ces avertissements - comme mentionné ailleurs - en pensant que vous définissez une valeur pour un ivar qui est un bloc) et un bloc de gestionnaire d'achèvement (dans lequel vous vous référerez sans aucun doute à vous-même).

Voici quelques directives du compilateur pour supprimer l'avertissement de cette seule ligne de code:

#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
la source
1

Ajout de deux cents sur l'amélioration de la précision et du style. Dans la plupart des cas, vous n'utiliserez qu'un ou deux membres de selfdans ce bloc, très probablement juste pour mettre à jour un curseur. Le casting selfest exagéré. Au lieu de cela, il vaut mieux être explicite et ne diffuser que les objets dont vous avez vraiment besoin à l'intérieur du bloc. Par exemple, si c'est une instance de UISlider*, disons, _timeSliderfaites simplement ce qui suit avant la déclaration de bloc:

UISlider* __weak slider = _timeSlider;

Ensuite, utilisez simplement l' sliderintérieur du bloc. Techniquement, cela est plus précis car il réduit le cycle de rétention potentiel à l'objet dont vous avez besoin, pas à tous les objets à l'intérieur self.

Exemple complet:

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

De plus, très probablement, l'objet projeté sur un pointeur faible est déjà un pointeur faible à l'intérieur, ce selfqui minimise ou élimine complètement la probabilité d'un cycle de rétention. Dans l'exemple ci-dessus, _timeSliderest en fait une propriété stockée en tant que référence faible, par exemple:

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

En termes de style de codage, comme avec C et C ++, les déclarations de variables sont mieux lues de droite à gauche. Déclarant SomeType* __weak variabledans cet ordre se lit plus naturellement de droite à gauche comme: variable is a weak pointer to SomeType.

Luis Artola
la source
1

J'ai rencontré cet avertissement récemment et je voulais mieux le comprendre. Après un peu d'essais et d'erreurs, j'ai découvert qu'elle provenait du fait qu'une méthode commence par "ajouter" ou "enregistrer". Objective C traite les noms de méthode commençant par "new", "alloc", etc. comme renvoyant un objet conservé mais ne mentionne (que je peux trouver) rien concernant "add" ou "save". Cependant, si j'utilise un nom de méthode de cette manière:

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

Je verrai l'avertissement sur la ligne [self done]. Cependant, cela ne:

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

Je vais aller de l'avant et utiliser la méthode "__weak __typeof (self) faiblesseSelf = self" pour référencer mon objet mais n'aime vraiment pas avoir à le faire car cela confondra un futur moi et / ou un autre développeur. Bien sûr, je ne pouvais pas non plus utiliser "ajouter" (ou "enregistrer") mais c'est pire car cela enlève le sens de la méthode.

Ray M.
la source