Faites attention aux NSTimer

08:28 modifié dans API AppKit #1
Salut à  tous !

Bon je suppose que la plupart d'entre vous le savez déjà , mais faites gaffes aux Timers :D
J'utilise un timer dans une sous classe de NSObject qui doit être relâchée à  un moment ou à  un autre pour pouvoir économiser un peu la mémoire.
Je m'étonnais de ne pas voir mon petit log "I have been released".

Donc j'ai jeté un oeil à  la docu des Timers (après avoir cherché 5 bonnes minutes d'où ça provenait) :


It is important to realize that, in keeping with standard Cocoa memory management rules, a timer maintains a strong reference to its target. (That is, in a reference-counted environment a timer retains it target, and in a garbage-collected environment it has a strong reference to the target.) This means that as long as a timer remains valid (and you otherwise properly abide by memory management rules), its target will not be deallocated. As a corollary, this means that it does not make sense for a timer's target to try to invalidate the timer in its dealloc or finalize method"neither method will be invoked as long as the timer is valid.


En gros (pour ceux qui n'aiment pas l'anglais :D ), le target de votre timer (donc généralement votre classe : self) ne pourra pas être "détruit" tant que le timer sera en marche.


Voilà  pour la petite info. Je sais pas si ça a déjà  été évoqué mais bon, c'est toujours bon à  savoir.

Réponses

  • schlumschlum Membre
    08:28 modifié #2
  • AliGatorAliGator Membre, Modérateur
    08:28 modifié #3
    En plus à  l'époque du post de MacBi tu avais mis un [tt]retain[/tt] sur ton timer et un [tt][timer release][/tt] après le [tt][timer invalidate][/tt]... alors qu'ils n'étaient pas nécessaire, étant de toute façon retenus par la RunLoop tant que le timer est actif ;)

    Bon heu donc si je comprend bien le problème, si j'ai une classe Toto qui a une variable d'instance [tt]NSTimer* timer;[/tt], et que typiquement j'ai
    @implementation Toto<br />- (id)init<br />{<br />&nbsp; &nbsp; self = [super init];<br />&nbsp; &nbsp; if (self) {<br />&nbsp; &nbsp; &nbsp; &nbsp; // ...<br />&nbsp; &nbsp; &nbsp; &nbsp; timer = [NSTimer scheduledTimerWithTimeInterval:... target:self selector:@selector(...) userInfo:... repeats:YES];<br />&nbsp; &nbsp; }<br />}<br />// ...<br />- (void)dealloc<br />{<br />&nbsp; &nbsp; // ...<br />&nbsp; &nbsp; [timer invalidate];<br />&nbsp; &nbsp; [super dealloc]<br />}<br />@end
    
    de sorte d'avoir mon timer qui tourne constamment tant que mon instance de Toto existe... ben en fait ça bloque la destruction de mon instance de Toto ?
    Car son retainCount ne descendra jamais à  zéro (et donc le dealloc ne sera jamais appelé)... à  moins que j'invalide le timer à  un moment dans une méthode, ce qui fait que le timer va faire un release sur son target... son retainCount passe à  zéro, il appelle dealloc... et fait un [tt][timer invalidate][/tt] qui du coup à  cet endroit ne sert plus à  rien ??

    C'est bizarre pourtant c'est un cas de figure assez classique je trouve, non ? Et j'ai toujours vu procéder comme ça, création du timer dans le init et invalidation dans le dealloc... du coup c'est quoi la préconisation dans ce genre de cas ?? ???
  • novembre 2008 modifié #4
    J'avais tenté ça Ali, mais sans succès.
    Obligé d'appeler un "destroyTimer:" à  ma classe avant de la relâcher.

    En gros ça donne ça :

    <br />// AppController.m<br />- (void)releaseToto<br />{<br />[toto destroyTimer];<br />[toto release];<br />}<br /><br />// Toto.m <br />- (id)init<br />{<br />&nbsp; &nbsp; self = [super init];<br />&nbsp; &nbsp; if (self) {<br />&nbsp; &nbsp; &nbsp; &nbsp; // ...<br />&nbsp; &nbsp; &nbsp; &nbsp; timer = [NSTimer scheduledTimerWithTimeInterval:... target:self selector:@selector(...) userInfo:... repeats:YES];<br />&nbsp; &nbsp; }<br />}<br />// ...<br />- (void)dealloc<br />{<br />&nbsp; &nbsp; // ...<br />&nbsp; &nbsp; [super dealloc]<br />}<br /><br />- (void)destroyTimer<br />{<br />[timer invalidate];<br />}<br />
    


    J'avais bêtement cherché s'il n'existe pas une méthode NSTimer du genre "removeFromTarget:"... un peu comme pour le notification center.. mais rien, quedal. Et quand bien même ça aurait existé, ça n'aurait pas marché puisque de toute façon le dealloc n'est pas appelé..
  • schlumschlum Membre
    08:28 modifié #5
    dans 1227921143:

    En plus à  l'époque du post de MacBi tu avais mis un [tt]retain[/tt] sur ton timer et un [tt][timer release][/tt] après le [tt][timer invalidate][/tt]... alors qu'ils n'étaient pas nécessaire, étant de toute façon retenus par la RunLoop tant que le timer est actif ;)


    Et j'utilisais un NSTimer au lieu de "performSelectorOnMainThread" beaucoup plus adéquat... Mais c'était il y a presque 3 ans, il y a prescription  :P
  • MalaMala Membre, Modérateur
    08:28 modifié #6
    dans 1227917294:

    En gros (pour ceux qui n'aiment pas l'anglais :D ), le target de votre timer (donc généralement votre classe : self) ne pourra pas être "détruit" tant que le timer sera en marche.


    Voilà  pour la petite info. Je sais pas si ça a déjà  été évoqué mais bon, c'est toujours bon à  savoir.


    Je ne connaissais pas cette subtilité. C'est bon à  savoir merci. 
  • schlumschlum Membre
    08:28 modifié #7
    dans 1227921143:
    C'est bizarre pourtant c'est un cas de figure assez classique je trouve, non ? Et j'ai toujours vu procéder comme ça, création du timer dans le init et invalidation dans le dealloc... du coup c'est quoi la préconisation dans ce genre de cas ?? ???


    Tu touches du doigt la bête noir des concepteurs de Garbage Collector  ;) (et des utilisateurs...)
    Un objet A référence un objet B qui lui même référence l'objet A (c'est le cycle le plus court, mais il peut y en avoir de bien plus grands !).

    Vu que la gestion de mémoire en Objective-C est une sorte de Garbage Collector manuel (l'utilisateur décide des compteurs de références), on tombe sur le même os...

    La seule préconisation contre ça, c'est ne ne jamais utiliser "this" comme target (et faire attention à  ce que la target n'ait aucune référence sur l'objet d'origine sinon on a un cycle encore plus grand et encore plus de problèmes !)
    Toutes les autres solutions sont de la bidouille (s'arranger pour tuer le timer au moment de la fermeture de la fenêtre, surcharger "release" pour tuer le timer quand le retainCount est à  1...)
Connectez-vous ou Inscrivez-vous pour répondre.