NSThread et mémoire
apprenti bidouille
Membre
Bonjour !
J'ai le schéma simplifié suivant :
Bien sûr sans la dernière ligne ( [thePath1 release]; ) mon application fuit en mémoire... (assez méchamment d'ailleurs ! )
Mais avec cette dernière ligne, cela ne fuit plus et tout se passe bien sauf qu'au bout de quelques secondes (quelques dizaines d'appels de rotationContinueEffective donc) l'application plante...
Visiblement je gère mal l'emplacement mémoire de theThread1... Mais alors quelle est la bonne méthode ?
Merci par avance....
J'ai le schéma simplifié suivant :
- (IBAction)rotationContinue:(id)sender<br />{<br />[NSThread detachNewThreadSelector:@selector(rotationContinueEffectiveThread) toTarget:self withObject:self]; <br />}<br /><br />-(void)rotationContinueEffectiveThread<br />{<br /> NSAutoreleasePool *pool=[[NSAutoreleasePool alloc]init];<br /> timer=[NSTimer scheduledTimerWithTimeInterval:csteRotation target:self selector:@selector(rotationContinueEffective) userInfo:self repeats:YES];<br /> [[NSRunLoop currentRunLoop]run];<br /> [pool release];<br />}<br /><br />-(void)rotationContinueEffective<br />{<br /> int index<br /> int borneSup=[arrayOfBezier count];<br /> for(index=0;index<borneSup;index++)<br /> {<br /> NSBezierPath *thePath1=[[NSBezierPath alloc]init];<br /> [......................]<br /><br /> [[arrayOfBezier objectAtIndex:index]replaceObjectAtIndex:0 withObject:thePath1];<br /> [thePath1 release];<br /> }<br />}<br />
Bien sûr sans la dernière ligne ( [thePath1 release]; ) mon application fuit en mémoire... (assez méchamment d'ailleurs ! )
Mais avec cette dernière ligne, cela ne fuit plus et tout se passe bien sauf qu'au bout de quelques secondes (quelques dizaines d'appels de rotationContinueEffective donc) l'application plante...
Visiblement je gère mal l'emplacement mémoire de theThread1... Mais alors quelle est la bonne méthode ?
Merci par avance....
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
[Edit] Ensuite, à mon avis il faut que tu lances ta runloop de cette manière :
À moins que le NSTimer soit inclus dans les "input sources", dans ce cas, le problème est ailleurs.
[Edit 2]
Ah, il y a une méthode :
Donc essaie d'ajouter :
Avant le "run"
Dans le cas des NSThread, la méthode passe en argument l'objet anArgument dans la méthode :
Dans le cas des NSTimer, l'argument passé c'est le timer en question qui permet de récupérer les userInfo de la méthode :
Ah... Je croyais que le thread était fermé quand il arrivait en fin de fonction ?
Euh ok je vais essayer ça mais je comprends pas trop le rapport avec mon problème... Ma runloop est visiblement bien lancée puisque le timer est appelé et tout... Je rappelle que si j'enlève le "[thePath1 release]" tout à la fin tout marche bien sauf que ça fuit en mémoire...
Enfin j'essaie ce que tu m'as dit néanmoins...
Merci de te pencher sur mon problème en tout cas, mais je pense qu'il faut chercher plus basique, j'ai jamais réellement bien saisi comment fonctionne l'allocation mémoire, et visiblement je dois mal gérer l'objet thePath1.... Et ça doit être lié au fait que c'est dans un nouveau NSThread parce qu'il n'y a pas de problème dans le cas contraire...
Sinon, je ne vois pas, il faudrait plus de code.
Pour finir, étant donné qu'un NSArray retient tous les objets qu'il contient, je te conseille d'utiliser la méthode +[NSBezierPath bezierPath] pour créer tes objets, en effet, tu les relâches à la fin de ta boucle donc ils n'ont aucune chance d'être supprimé avant que tu aies terminé le traitement dessus.
Ensuite, je te conseille, en tout cas pour la première boucle d'utiliser un énumérateur plutôt que d'accéder aux membres de ton NSArray via la méthode -[NSArray objectAtIndex:] c'est beaucoup plus sécurisé. C'est très simple :
Ce genre de fonctionnement est déjà beaucoup plus propre.
Mauvaise idée, vu que son thread est fait pour durer et exécuter ce truc à l'infini, la mémoire ne serait jamais libérée. Ou alors il faut un NSAutoRelease local.
Quand on peut, il vaut mieux utiliser l'allocation de mémoire classique, c'est ça qui est beaucoup plus propre !
D'ailleurs, je viens de me rappeler de quelque chose. La méthode qui contient cette énumération et qui modifie des NSBezierPath... Elle est appelée par un NSTimer, il s'agit d'un événement, or Cocoa crée une NSAutoreleasePool pour encapsuler chaque événement. Donc à la fin de l'énumération et donc à la fin de la méthode tous les objets qui y ont reçus un message -[NSObject autorelease] sont supprimés.
Il n'y a donc pas de surcharge de mémoire à cause des autoreleases et des énumérations.
Sinon, si Cocoa demande à ce que dans la méthode appelée par le nouveau NSThread on ajoute une NSAutoreleasePool c'est simplement parce que Cocoa en a besoin pour la base, mais pour tous les événements "normaux" (clics, timers, etc.) il en génère au moins une.
à‰crire soit-même une autorelease pool n'est utile que dans le cas d'un nouveau thread ou dans le cas d'une boucle très longue, donc à moins qu'il n'ait mis un bon millier d'éléments dans son NSArray, l'autorelease pool locale n'est pas justifiée dans la boucle et on peut donc laisser à celle qui est créée par et pour le timer le loisir de nettoyer les objets après la sortie de la méthode.
Ca n'empêche qu'il faut éviter de créer des objets auto-releasés quand on peut... Ceci-dit, c'est pas ça qui va résoudre son problème, là on joue avec les diverses manières de gérer la mémoire pour un même résultat final ???
Sinon, je ne vois pas en quoi les autoreleases sont à éviter c'est un mécanisme utilisé partout dans Cocoa, il ne dispense pas d'être vigilant mais peut aider à éviter des manipulations hasardeuses.
C'est le débat du tout auto-release / Garbage Collector / Gestion manuelle de la mémoire...
Les mécanismes d'auto-release sont bien pratiques pour éviter les callback, mais c'est quand même un mécanisme assez lourd qui, si on ne fait pas attention, peut soit remplir très vite une grosse partie de la mémoire au point de devoir déborder sur la swap et d'avoir des ralentissement, soit donner des ralentissements au moment où de nombreux objets sont relâchés.
http://www.objective-cocoa.org/forum/index.php/topic,2269.0.html
Affirmatif ;-)
Pas trop compris... Il ouvre bien GDB.... Mais pas de message d'erreur !
Merci beaucoup, je ne connaissais pas cette méthode :-)
Oui je confirme, j'ai besoin du alloc...
Donc pour reprendre... En fait j'ai un tableau arrayOfBezier dans lequel chaque ligne est constituée d'un bezierPath et d'un tableau de coordonnées de points...
La fonction rotationContinue est chargée d'appliquer une rotation sur les tableaux des coordonnées de points, et d'en faire des bezierPath qu'elle remet dans arrayOfBezier.
Or si je ne release pas thePath1 à la fin de ma fonction de rotation, ça fuit.. Jusque là c'est logique..
Donc la logique est de releaser thePath1 une fois qu'il a été mis dans le tableau. Ce que je fais. Du coup ça ne fuit plus. Si ma fonction rotationContinue est appelée dans le thread principal, tout roule.
Néanmoins, si ma fonction rotation est appelée par rotationContinueEffectiveThread, qui crée un nouveau thread juste pour lui, tout se passe bien pendant un certain temps (en fait une bonne centaine d'appels peut-être) et puis au bout d'un moment boum, ça plante...
Apparemment quand il plante, c'est quand il arrive dans le drawRect :
Au bout d'un moment il ne trouve plus l'élément [[arrayOfBezier objectAtIndex:i]objectAtIndex:0].... Enfin voilà j'espère que c'est à peu près clair...
Le truc vraiment bizarre c'est que le problème n'apparaisse que quand la fonction est appelée par un thread autre que le thread principal...
Merci de m'aider dans tous les cas !
Je ne comprends pas pourquoi tu as besoin de stocker un NSBezierPath suivit d'une série de point dans ton tableau. à‰tant donné qu'un NSBezierPath est déjà un tableau de points, de plus tu peux y appliquer une NSAffineTransform qui permet la rotation de l'objet. Apparemment tu reconstruits ton bezierPath à chaque fois, étant donné les outils fournis dans cette classe, ce que tu fais est totalement anti-programmation objet...
Bon, je t'explique ce qu'il se passe chez moi.
Pour commencer, dans mes préférences XCode (pour pouvoir modifier l'option il faut qu'aucun projet ne soit ouvert, de plus cette option n'existe que pour la version de Tiger), j'ai mis dans la partie "General", le "layout" à "all-in-one" ce qui fait que sur une seule fenêtre j'ai tout ce qui concerne mon projet, à savoir :
Lorsque mon application plante donc, le projet se place sur la page "Debug" directement, ce qui me permet de voir la trace d'exécution les données qu'il y avait dans la mémoire au moment du plantage. Mais, en forçant l'exécution du programme dans GDB jusqu'au plantage définitif, lorsque je regarde dans "Build", souvent je vois des messages du genre "uncaught exception :..." ou d'autres types d'erreurs.
Si tu es sous XCode 2.x (sous Tiger donc) et que t'es en mode "All-in-one", est-ce que des messages s'affichent dans la console de la page "Build" ?
Merci quand même en tout cas
Avec le peu de code que tu nous as fourni, j'ai refait un petit projet test... qui fonctionne bien sûr chez moi !
Comme tu ne nous donnes pas le crash log, c'est très difficile de t'aider plus.
Mais, garde à l'esprit que les classes NSMutable... ne sont pas thread safe, donc c'est avec beaucoup de précaution que tu dois les modifier dans ton thread, puis de les lire (dans drawRect:) à partir du main thread sous peine de ... crash.
.
Merci beaucoup à tous en tout cas !
Merci quand même ;-)
J'essaie de faire un projet minimal qui pose souci, mais je pense que le problème vient des NSMutable comme me le soulignait Bru... je vais donc tâcher de voir comment y remédier !
Encore une fois le code fourni ici est quelque chose de minimal qui ne correspond en fait à rien dans mon code, donc inutile de me dire qu'il vaut mieux utiliser les NSTransform etc, je connais...
La question est donc juste : pourquoi plante-t-il ?
Sachant que la réponse est a priori triviale pour quelqu'un qui a bien saisi ce qui se passe en mémoire : ce qui n'est visiblement pas du tout mon cas !
Voilà donc merci à ceux qui voudront se pencher sur mon problème, sûrement ridicule
[Fichier joint supprimé par l'administrateur]
À la fois tu essaies de dessiner un NSBezierPath (dans "drawRect"), et tu le modifies (dans "rotationContinueEffective")
J'ai entouré le contenu de ces deux fonctions (de la classe "MyView") par :
Et ça m'a l'air stable depuis tout à l'heure (bien que ça m'utilise 143% de CPU et que ça fait souffler mes ventilos !).
[Edit] PS : "@synchronized(arrayOfBezier)" devrait suffire...
Je sais pas comment ça s'utilise... Pour l'instant chez moi ça plante toujours avec ça... Vais me documenter
Merci beaucoup en tout cas ! :-) :-)
http://en.wikipedia.org/wiki/Thread-safety
http://developer.apple.com/documentation/Cocoa/Conceptual/Multithreading/articles/CocoaLocks.html
Mes premières impressions sont que ton code est illisible... Pour l'amour de Dieu ! Aère ton code ! C'est extrêmement important, même si c'est pas le code final il faut absolument l'aérer parce que c'est carrément impossible à lire.
Ensuite, tu te fais chier à créer des NSString pour contenir des variables floats... ???
Pourquoi tu n'utilises pas la classe NSNumber ?
Elle est faite pour contenir des variables numériques (entier, flottant, bool, double, ce que tu veux !) pour pouvoir mettre ces objets dans une collection !
Essaye ensuite de répartir un peu ton code, fais des variables temporaires qui vont contenir une valeur que tu vas utiliser plusieurs fois.
Pourquoi n'utilises-tu pas les outils que te fournit Cocoa ? Par exemple, la classe NSValue possède des constructeurs pour des objets NSSize, NSPoint et NSRect...
Dans ta méthode -drawRect:, il est totalement inutile (voire même dangereux) d'appeler la méthode de la super classe, pour plusieurs raisons. Déjà ce n'est pas [self frame] qu'il aurait fallu passé mais "rect", et ensuite parce que drawRect: n'est pas définie dans la super-classe, NSView est une classe abstraite et -drawRect: une méthode abstraite qui ne fait donc rien au départ.
Je te conseille aussi, au lieu d'utiliser un timer, d'utiliser dans ton thread séparé, la méthode : +[NSThread sleepUntilDate:] en utilisant une boucle infinie et une NSDate, de cette manière :
Et pas besoin de runLoop. De plus inutile de mettre des userInfo dans un timer si tu ne les utilises pas; Il faut aussi savoir que l'argument d'une méthode appelée par un timer est un NSTimer, et c'est même le NSTimer qui a fait l'appel.
Je pense aussi que tu peux remplacer tes NSArray par des NSDictionary, en utilisant des constantes en guises de clés.
Par exemple :
Je pense que ça évitera beaucoup d'erreurs...
Je vais continuer à fouiller ton code.
Essaye de mieux organiser ton code, et surtout simplifie-le, là en l'occurrence il est extrêmement tordu et doit être super lourd à l'exécution...