[Résolu] Mémoire - ImageIO_PNG_DATA - weak/strong
Bonjour,
suite à ce post, et les conseils de Draken, je teste la consommation mémoire dans le Debug Navigator, et je remarque un truc grave.
Le fait de faire un push vers un second VC puis un popToRoot vers le premier VC, puis de recommencer cela plusieurs fois, ne fait qu'augmenter la consommation mémoire (seulement dans le sens push).
Environ 12Mo à chaque fois.
J'ai donc tester sur plusieurs de mes projets (simple et complexes), et à chaque fois, ça augmente.
Comment revenir à la consommation précédent le push.
On dirait que la consommation due au push s'accumule à chaque fois, et ne redescend pas lors du popToRoot.
J'ai lu ça, enfin parcouru, parce que c'est tout en anglais et hyper long.
C'est sur ça :
strong reference cycles
J'ai cru comprendre en fouillant sur le web, que le problème pouvait venir de là .
Mais je ne sais pas comment faire.
En attendant, je rajoute ça sur le premier VC :
etape1ViewController = UIStoryboard.etape1ViewController()
etape1ViewController.removeFromParentViewController()
etape1ViewController = nil
et ça sur le second VC :
etape0ViewController = UIStoryboard.etape0ViewController()
etape0ViewController.removeFromParentViewController()
etape0ViewController = nil
et ça ne change rien.
Pourtant, en faisant un println je vois bien que les VC sont chacun leur tour "nil".
Je pensais que en faisant ça, les variables associées aux VC seraient vidées ?
Quelqu'un a-t-il une idée ?
Merci.
Réponses
Heu c'est pas une méthode, c'est une classe.
C'est le controller associé à la view correspondante.
Idem pour etape1
Et dedans, y'a plein de méthodes, et de variables. Normal, quoi.
Je ne comprend pas comment on vide les variables d'un controller que l'on quitte pour un autre.
J'ai aussi testé ça :
ça change rien
J'ai aussi essayé ça :
ça change rien, toujours 13Mo de plus à chaque push
En testant dans Instruments, dans "Leaks", et je n'ai pas de "leaks".
Par contre, je remarque dans les stats que la ligne "ImageIO_PNG_DATA", augmente justement de 13Mo à chaque "push", dans la colonne "Persistent bytes", alors que mon dossier d'images fait 3Mo, et que la photo chargée dans la view fait 280Ko.
Y'a cette page qui nomme la chose.
Il résout le pb avec une librairie, mais je ne comprend rien à l'install.
Et est-ce vraiment nécessaire de mettre une librairie. Y'a surement un truc à faire dans mon code...
Tu as écrit :
UIStoryboard ne possède pas la méthode de classe etape1ViewController()
Comment as-tu ajouté ce méthode de classe et qu'est-ce que tu as écrit là dedans ?
Sans quoi, je suis tombé là -dessus.
Donc, j'apprend que l'on peut maintenant faire ça, et pas de pb de mémoire.
Mais du coup, j'essaie quand même de remplacer mes
par
et ça
par ça
Donc je fais ça dans tous mes fichiers, et je retisserais.
Il faut bien essayé, et comme je n'ai pas la pure culture et l'historique de Xcode, je n'ai pas le choix.
Merci pour tes efforts
Bon, ben ça ne change rien.
Ni dans Instrument/Leaks, et ni dans le Debug Navigator.
Donc, toujours pas de solutions, ou de piste ?
Merci AliGator. ça a l'air bien ton truc, mais trop compliqué pour moi. Et puis je la trouve pas mal cette petite classe, qui n'est pas de moi d'ailleurs. ça vient d'un toto, je sais plus lequel. Mais j'aime bien le principe.
Pour les class fun, un p'tit copier coller et hop. C'est pas grand chose.
En attendant t'as une idée pour la mémoire et les images et apparemment une histoire de cache ?
D'après ce post, il semblerait que ce problème n'apparaisse que sur un simulator, et non pas sur un device.
Des infos là -dessus ?
Si c'est vrai, ça me rassure.
Je rappelle le problème de base.
dans Instruments/Leaks (ImageIO_PNG_DATA) et aussi dans le debug navigator, la mémoire augmente sans cesse.
La mémoire réservée par le système pour les images ne se vide jamais.
Heu le README ne serait pas clair ?
Il suffit d'exécuter ceci dans ton terminal et c'est fini, il te génère tout seul le code avec toutes les "class func" pour ton UIStoryboard dans "Storyboard.swift"... c'est pas sorcier quand même, on peut difficilement faire plus simple que de faire ça en une seule ligne !
---
Sinon, pour ton problème de mémoire, "UIImage(named: xxx)" garde en cache les images (dans un NSCache interne, ce qui est donc tout à fait adapté, et se vide tout seul dès réception d'un Memory Warning pour libérer la mémoire à la moindre demande / au moindre besoin). C'est très bien implémenté et permet à la fois d'avoir un chargement très rapide des images que tu as déjà utilisées avant dans ton code, et de libérer la mémoire tout seul quand le système en demande. Mais évidemment, du coup à chaque fois que tu demandes une nouvelle UIImage avec un nom d'image que tu n'as pas encore demandé avant, ça fait un pic mémoire qui ne sera pas vidé immédiatement (car c'est le but, garder l'image en cache), mais sera automatiquement vidé si le système a besoin de ressources. Tu peux voir justement si ces pics mémoires et usage de la RAM redescendent tout seul si tu simules un MemoryWarning dans ton simulateur.
Merci Alligator pour tes explications, qui commencent à m'éclairer.
1- Concernant ton outils, tu m'as convaincu.
J'essaierai, un de ces quatre, la ligne de commande, ça a l'air super intéressant.
Ton readMe est super clair, sauf que c'est moi qui a un peu de mal avec l'anglais.
2- Concernant UIImage :
Tu conseilles donc d'utiliser
plutôt que
Parce que j'ai lu tellement de choses là -dessus, que je ne sais plus à quel saint me vouer.
Tu sembles confirmer que
soit l'idéal. Mais je me pose des questions concernant les images qui sont réutilisées et celles qui sont ponctuelles.
Voici donc le type d'images que j'utilise :
Toutes les images sont en 264dpi - Le projet ne tournera que sur des iPad2.
Des images de fond en jpg placées sur toutes les Views dans le storyBoard - 163Ko - 1024x768
D'autres images de fond en jpg placées en code sur peu de Views - 148Ko, etc.
Des p'tits pictos en png placés en code sur toutes les Views - 11Ko
Et surtout des photos qui chargées par l'utilisateur d'après l'album, l'appareil photo, ou téléchargées. Pour le dév. j'utilise des photos en jpg de 560x420 - 250Ko.
Quand je passe du controller VC1 à VC2, la mémoire consommée augmente - Donc, là , ok - Mais quand je passe ensuite de VC2 à VC1 puis à nouveau de VC1 à VC2, la mémoire augmente encore (et cela avec les deux techniques UIImage). J'ai donc l'impression que l'image est stockée plusieurs fois en cache. C'est très bizarre, d'autant plus que j'ai lu plein de posts qui soulignent ce fait. On le remarque dans Instruments/Leaks sur "ImageIO_PNG_DATA)".
Je peux faire monter la mémoire consommée au-delà de 500Mo, en faisant VC1 -> VC2 puis VC2 -> VC1 puis VC1 -< VC2 etc.
La mémoire ne se vide jamais. Cela veut-il dire que le système n'a pas besoin de mémoire ? Plus de 500Mo, c'est quand même chaud, non ? Et surtout que c'est simplement en faisant "VC1 -> VC2 puis VC2 -> VC1 puis VC1 -< VC2" - Alors qu'ensuite, lorsque je navigue dans le projet (dans un PageView entre autres) et que j'utilise toutes mes fonctionnalités, cela n'augmente pas la mémoire consommée.
3- Concernant memory warnings :
J'ai fait "Simulate Memory Warning", aussi bien dans le simulator que dans Instruments. Pour ce faire, j'ai fait ça :
puis "Simulate ...."" dans le soft.
J'ai aussi essayé sur un nouveau projet vide.
J'ai exactement les mêmes infos - Et cela, uniquement au démarrage - à savoir :
Puis lorsque je navigue (VC1 -> VC2 puis VC2 -> VC1 puis VC1 -< VC2 etc), je n'ai aucun message supplémentaire.
Je ne vois pas trop comment utiliser ces simulations de memory warnings. En tout cas, cela ne m'aide pas pour éviter que la mémoire consommée augmente indéfiniment.
Merci d'avance pour ta réponse.
Pour info, je récupère un vieil iPad2 dans une semaine - J'ai hâte.
Ben je vois que "ImageIO_PNG_DATA" (dans Instruments/Leaks) augmente sans cesse quand je navigue vc1 / vc2 / vc1 / vc2 etc.
Et à chaque fois de 13Mo.
Puis quand j'explore le détail, je vois qu'il y a des images de 12Mo :
Je ne sais pas, d'ailleurs comment repérer les images en fonction de" l'Adresse".
J'ai mis une vidéo ici, où l'on voit bien la mémoire de "ImageIO_PNG_DATA" augmentée indéfiniment.
Au passage l'extension UIStoryboard vient de là .
Thanks Ray...
T'en penses quoi du coup de mon "ImageIO_PNG_DATA" ?
Ah désolé, encore merci.
Sans connaà®tre rien à Swift et à ton projet, mais en Objective-C, je checkerais ça :
deinit (équivalent dealloc) est-il appelé ?
Des NSTimers ?
Des delegates strong ? De ce que j'ai lu rapidement, les var sont à strong par défaut.
Des observers ? (addObserver:zzz:)
Merci Larme pour tes précisions.
J'avais vu ça aussi. Et il faut donc faire :
plutôt que :
Mais quand je lance l'appli, j'ai une erreur (ça plante -> nil...) dès que j'utilise :
D'autre part si je fais :
J'ai cette erreur :
Génial le weak !!!
Je ne sais pas me servir de ces trucs là , mais je vais faire des recherches...
Je n'ai aucun (pour l'instant) timer. Et si ça devient le cas, je ne sais pas ce à quoi tu fais référence.
Alors là , carrément, je suis complètement inculte sur ce sujet.
Je connais les écouteurs en ActionScript, je pense que c'est un peu la même chose.
Je vais faire des recherches.
Pour le coup des images, t'as pas d'idée ?
Merci encore pour ces pistes à suivre.
Forcément, weak n'est applicable que sur une variable contenant un objet. Un CGFloat n'est pas un objet, c'est juste un nombre.
Le mécanisme de gestion mémoire de Swift est efficace, mais il ne sait pas gérer les références croisées.
Si un objet A contient une référence sur l'objet B, et que B contient une référence sur l'objet A, il y a un problème. A ne peut être détruit que si B est détruit. Et B ne peut être détruit que si A n'existe plus, une situation paradoxale !
Cela arrive chaque fois qu'un objet possède un lien sur son "supérieur". Par exemple une vue avec une référence sur son contrôleur.
Pour éviter ça, on utilise les références fortes et les références faibles. Si un objet B possède une référence faible sur un objet A, le gestionnaire de mémoire sait qu'il peut le détruire, même si A existe encore.
// Problème
A possède une référence sur B
B possède une référence sur A
// Solution
A possède une référence forte sur B
B possède une référence faible sur A
Par défaut, toutes les références sont fortes.
Pour créer une référence faible, il faut ajouter l'attribut weak.
Merci Draken.
Oui, ça je sais.
Ok pour le CGFloat. Merci.
Mais, dixit la première erreur dont je parle, comment fait-on pour mettre un weak sur un objet (weak var viewVoletTop: UIView!), alors que dès qu'on l'utilise (viewVoletTop) on tombe en erreur ?
Merci
A mon avis tu n'as pas initialisé viewVoletTop.
Ouaaah, je n'ai donc rien compris.
En swift, je croyais que (avec weak ou pas)
était l'initialisation, puis, par exemple
était l'instanciation.
Désolé, mais je ne comprend pas ce que tu veux dire ...
Pour avoir une référence weak qui restera valide, il faut "tenir" l'objet dans une autre référence strong, autrement il n'y aura rien à empêcher l'objet de mourir.
ça c'est la déclaration de la variable et non l'initialisation.
On peut aussi utiliser la formulation suivante pour déclarer et initialiser en même temps :
ou encore
Et initialiser la frame de la vue dans le viewDidLoad.
L'initialisation, c'est quand tu lui donnes une valeur initiale.
---
Donc par exemple : C'est une déclaration, tu déclares que tu as une variable nommée "viewVoletTop" et que son type est "UIView!". Tu déclares qu'elle existe et quel est son type, mais tu pour l'instant elle n'a aucune valeur dedans, tu dis juste qu'elle existe. Qui dit déclaration de variable/constante dit mot clé "var"/"let" (respectivement).
---
Alors qu'ensuite quand tu écris : Tu affectes une valeur initiale à cette propriété/variable, c'est bien une initialisation (qui dit initialisation dit affectation d'une valeur à la variable, donc dit forcément un "=" quelque part)
Après, tu peux faire les deux en même temps, ainsi : Est à la fois une déclaration et une initialisation en une seule ligne de code. Tu déclares à la fois qu'il existe une variable ("var") qui porte le nom "viewVoletTop" et que son type (":") est "UIView!", tout ça c'est la partie "déclaration", et dans la même ligne tu lui donnes une valeur initiale (tu l'initialises) avec le "= UIView()" ("UIView()" va créer une instance de la classe UIView, donc va créer un objet de type UIView, et ensuite tu affectes cette nouvelle instance / ce nouvel objet dans ta variable viewVoletTop)
---
Il se trouve que Swift étant assez intelligent pour inférer {déduire) le type des variables quand le contexte le lui permet, donc de même quand tu écris : TU fais également à la fois une déclaration et une initialisation en une seule ligne. Cette ligne est en fait exactement l'équivalent de la ligne que j'ai écrite juste avant, sauf que quand tu *déclares* ta variable avec "var viewVoletTop" tu ne précises pas *explicitement* son type, mais Swift sait le déduire car il voit que tu en profites pour aussi *initialiser* cette variable avec un objet de type "UIView" (car l'objet créé par le constructeur "UIView()" est forcément un objet de type UIView) et donc il va en déduire que cette variable "viewVoletTop" bah son type c'est certainement "UIView", et donc que la ligne que tu as écrit revient à la même chose que si tu avais écrit implicitement : NB : Note d'ailleurs au passage que ce n'est donc pas *exactement* la même chose qu'avant, car dans ce cas il considère que ta variable est de type non-optional ("UIView") alors qu'en la typant explicitement avec ": UIView!" tu lui dis que c'est une "implicitly-unwrapped-optional" donc une "UIView!" et pas juste une "UIView"...
Bref, déclaration c'est quand tu as "var" ou "let". initialisation c'est quand tu affectes une valeur initiale à ta variable ou constante avec un "=". Chose qui peut se faire soit sur la même ligne que la déclaration, soit dans le "init" (le constructeur) de ta classe.