Blocs et variables d'instance
Céroce
Membre, Modérateur
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:
- 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
Auriez-vous une explication ? Je ne comprends absolument pas pourquoi utiliser la variable d'instance fait que l'objet est retenu.
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:
Quelques explications:
- (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];
}];
}
- 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.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
le compilateur transforme tout accès à une variable d'instance en self ->variableInstance, de coup ça retient le self.
Donc :
est équivalent à
La solution à ton problème est :
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 :
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 : (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.
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.
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é.