web-dev-qa-db-fra.com

se capturer fortement dans ce bloc est susceptible de conduire à un cycle de rétention

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
}];
204
user1845209

La capture de self arrive avec votre accès implicite à la propriété de self.timerDisp - vous ne pouvez pas faire référence à self ou aux propriétés de self à l'intérieur d'un bloc qui sera fortement retenu par self.

Vous pouvez contourner ce problème en créant une faible référence à self avant d'accéder à timerDisp dans 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]];
                                            }];
508
Tim
__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 un bloc, utilisez-les comme propriétés d'un objet faible, exemple:

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 transmettez une copie faible d'un objet non conservé par quiconque:

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

si vcToGo sera désalloué puis ce bloc sera activé, je pense que vous allez planter avec un sélecteur non reconnu dans une corbeille contenant la variable vcToGo_ maintenant. Essayez de le contrôler.

52
iiFreeman

Meilleure version

___strong typeof(self) strongSelf = weakSelf;
_

Créez une référence forte à cette version faible en tant que première ligne de votre bloc. Si self existe toujours lorsque le bloc commence à s’exécuter et n’a pas été ramené à zéro, cette ligne garantit qu’elle persiste pendant toute la durée de vie 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. C'est un excellent article de Erica Sadun sur Comment é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 succès serait:

_func doSomeThingWithSuccessBlock(success: () -> ()) {
    success()
}
_

Lorsque nous appelons cette méthode et que nous devons utiliser self dans le bloc de succès. 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 le projet open source populaire Alamofire.

Pour plus d'informations, consultez Swift-style-guide

41

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

vous ne pouvez pas faire référence à soi ou à ses propriétés de l'intérieur d'un bloc qui sera fortement retenu par soi-même.

Ce n’est pas tout à fait vrai. C’est bien pour vous de faire cela tant que vous interrompez le cycle à un moment donné. Par exemple, supposons que vous avez une minuterie qui se déclenche avec un bloc qui se conserve et que vous gardez également une référence forte à la minuterie. Cela convient parfaitement si vous savez toujours que vous allez détruire le chronomètre à un moment donné et briser le cycle.

Dans mon cas tout à l’heure, j’avais cet avertissement concernant le code:

[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’ya pas de danger de boucle de retenue, j’ai donc changé le nom de la méthode en "useY": Bien sûr, cela peut ne pas être approprié dans tous les cas et vous voudrez généralement utiliser une référence faible, mais J'ai pensé que cela valait la peine de noter ma solution au cas où cela aiderait les autres.

15
Chris Suter

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 à leur UIPageViewController, qui inclut une méthode définie (qui déclenche ces avertissements - comme mentionné ailleurs - pensant que vous définissez une valeur sur une 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 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
3
bshirley

Ajouter deux centimes 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 self dans ce bloc, probablement pour mettre à jour un curseur. Casting self est excessif. Au lieu de cela, il vaut mieux être explicite et transtyper niquement les objets dont vous avez réellement besoin à l'intérieur du bloc. Par exemple, s'il s'agit d'une instance de UISlider*, par exemple, _timeSlider, procédez comme suit avant la déclaration de bloc:

UISlider* __weak slider = _timeSlider;

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

Exemple complet:

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

En outre, il est fort probable que l'objet en cours de conversion en un pointeur faible soit déjà un pointeur faible à l'intérieur de self ainsi que de minimiser ou d'éliminer complètement la probabilité d'un cycle de rétention. Dans l'exemple ci-dessus, _timeSlider est en réalité 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 en C et C++, les déclarations de variable se lisent mieux de droite à gauche. Déclarer SomeType* __weak variable dans cet ordre se lit plus naturellement de droite à gauche comme suit: variable is a weak pointer to SomeType.

1
Luis Artola

Je suis tombé sur cet avertissement récemment et je voulais le comprendre un peu mieux. Après un peu d'essais et d'erreurs, j'ai découvert que cela provient du fait qu'une méthode commence par "add" ou par "save". Objective C traite les noms de méthodes commençant par "new", "alloc", etc. comme renvoyant un objet conservé, mais ne mentionne rien (que je puisse trouver) au sujet de "add" ou de "save". Cependant, si j'utilise un nom de méthode de cette façon:

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

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

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

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

1
Ray M.