Blocs et variables d'instance

Bonjour à  tous,

J'ai eu des problèmes mémoire sur un objet, et je me doutais que l'objet était retenu par un bloc. Voici le code qui pose problème:


- (void) setOverlay:(LTMOverlay *)overlay
{
_overlay = overlay;

__weak LTMOverlayRepresentation *weakSelf = self;
self.xObserver = [LTMKeyValueObserver observerForKeyPath:@x ofObject:_overlay changeHandler:^(NSDictionary *change) {
CGRect frame = weakSelf.representationView.frame;
frame.origin.x = _overlay.x; // **** Problème *****
weakSelf.representationView.frame = frame;
[weakSelf _updateSelectionPosition];
}];
}
Quelques explications:
- j'ai une propriété @property (nonatomic, strong) LTMOverlay *overlay;
- LTMKeyValueObserver est une classe perso qui implémente le KVO et appelle un bloc quand la valeur observé change.

Le problème est que le bloc retient l'objet (pas l'objet overlay, mais l'objet qui observe).

Maintenant si je remplace la ligne
frame.origin.x = _overlay.x;
par
frame.origin.x = weakSelf.overlay.x;
ça fonctionne!
Auriez-vous une explication ? Je ne comprends absolument pas pourquoi utiliser la variable d'instance fait que l'objet est retenu.

Réponses

  • samirsamir Membre
    septembre 2014 modifié #2

    le compilateur transforme tout accès à  une variable d'instance  en self ->variableInstance, de coup ça retient le self.


     


    Donc :



    frame.origin.x = _overlay.x;

    est équivalent à  



    frame.origin.x = self->overlay.x;

    La solution à  ton problème est :



     frame.origin.x = weakSelf->overlay.x;
  • AliGatorAliGator Membre, Modérateur
    septembre 2014 modifié #3
    Oui je confirme. Si tu accèdes directement à  une variable d'instance comme _overlay c'est comme si tu faisais self->_overlay. Ou self.overlay si tu as une propriété associée à  la variable d'instance.

    Mais comme je le répète depuis un bout de temps maintenant, surtout depuis que les @property sont auto-synthétisées en +, je déconseille fortement la déclaration et l'usage des variables d'instance " à  part dans le init où c'est justifié, ou si tu surcharges le setter/getter de l'ivar évidemment " et incite tout le monde à  n'utiliser que des @property et plus jamais des ivars déclarées dans l'@interface. Et du coup systématiquement utiliser self.propriété et non pas _variableInstance. Ca évite ainsi quelques surprises, dont celle dont tu viens de faire les frais.


    C'est d'ailleurs pour cela qu'en Swift, il n'y a plus de distinction entre propriété et variable d'instance (tout est propriété, comme ça c'est plus clair) et l'utilisation de "self." est forcée quand tu l'utilises dans un block / une closure pour que ça soit suffisamment explicite que self est utilisé donc retenu :
  • AliGatorAliGator Membre, Modérateur
    Sinon au passage, c'est bien d'avoir fait une variable weakSelf pour qu'elle ne soit pas capturée par le block, par contre il faut avoir conscience qu'une variable weak peut à  tout moment être remise à  nil.

    Si tu n'as vraiment pas de bol, et qu'un autre thread vient supprimer ton objet weakSelf pile poil entre le moment où tu as exécuté par exemple "weakSelf.representationView.frame = frame;" et la ligne suivante "[weakSelf _updateSelectionPosition];" bah la première des 2 lignes sera bien exécutée, mais la ligne suivante weakSelf sera repassé à  nil et donc _updateSelectionPosition ne sera pas appelé (puisque l'objet de toute façon n'existera plus).

    Selon ton cas, ce n'est peut-être pas forcément gênant. Mais peut-être que si, à  toi de voir, en tout cas faut en être conscient. Si tu as besoin d'être sûr que l'objet existe pendant tout le temps où le block va s'exécuter et ne risque pas d'être remis à  nil entre le début du block et la fin du block, rien de plus simple : il suffit de recréer une strong reference à  cet objet au début du block, et d'utiliser cette strong reference au lieu de la weak reference.

    Ce qui donne un pattern du genre :
    __weak typeof(self) weakSelf = self; // On crée une weak ref de self
    [xxx methodWithBlock:^(...) {
    __strong typeof(weakSelf) strongSelf = weakSelf; // On recrée une strong reference de weakSelf
    // Puis tu utilises strongSelf dans ton block
    }];
    (Note que le mot clé "__strong" n'est pas obligatoire, une variable locale étant strong par défaut)

    Ainsi, le temps de passer la variable au block tu crées une weak reference, pour pas que le block retienne la variable ; mais dès que le block commence à  être exécuté (potentiellement bien plus tard), tu recrées une strong reference locale pour être sûr que l'objet va être retenu au moins le temps du block.
  • CéroceCéroce Membre, Modérateur
    septembre 2014 modifié #5
    Bon, merci à  vous deux de confirmer le comportement que j'ai observé et que je trouve aberrant. Il faut le savoir, en fait.
     

    Mais comme je le répète depuis un bout de temps maintenant, surtout depuis que les @property sont auto-synthétisées en +, je déconseille fortement la déclaration et l'usage des variables d'instance " à  part dans le init où c'est justifié, ou si tu surcharges le setter/getter de l'ivar évidemment " et incite tout le monde à  n'utiliser que des @property et plus jamais des ivars déclarées dans l'@interface. Et du coup systématiquement utiliser self.propriété et non pas _variableInstance. Ca évite ainsi quelques surprises, dont celle dont tu viens de faire les frais.

    Oui, enfin là , je suis dans le setter quand même. S'il y a bien un endroit où je suis autorisé à  utiliser l'ivar, c'est bien ici.
     

    Sinon au passage, c'est bien d'avoir fait une variable weakSelf pour qu'elle ne soit pas capturée par le block, par contre il faut avoir conscience qu'une variable weak peut à  tout moment être remise à  nil.

    J'en suis bien conscient, mais effectivement, le problème ne se pose pas. Le KVObserver est une propriété privée de ma classe; il sera donc désalloué avant mon object. Ainsi le bloc ne sera jamais appelé une fois que mon objet est désalloué.
Connectez-vous ou Inscrivez-vous pour répondre.