Utilisation des NSMutableArray
Bonjour à toutes et à tous,
Je suis en train de développer une application qui :
- fait appel à un webService
- récupère un objet JSON
- "décortique" cet objet JSON et affiche le résultat dans une UITableView
L'objet JSON contient une liste (array) d'objets. Je récupère donc ces objets dans une NSMutableArray.
Jusque là pas de problème sauf que dès que je sors de la boucle FOR qui associe chaque sous objet à ma liste, je n'ai plus accès qu'au dernier objet inséré dans la liste. (Si je pointe à l'index 1 ou l'index 4 l'objet est le même alors qu'il ne l'était pas avant).
Ma question est la suivante : Sous certaines conditions, est-ce qu'une NSMutableArray a une portée(accessibilité) limité dans une fonction?
Voici un extrait de mon code:
listJsonTopArticle = [[NSMutableArray alloc] init];
OneOfTopArticle *myOneOfTopArticle = [OneOfTopArticle instance];
for(int i = 0; i < [resultRequest count]; i++)
{
[myOneOfTopArticle splitJsonReceived:[resultRequest objectAtIndex:i]];
[self.listJsonTopArticle addObject:myOneOfTopArticle];
[myOneOfTopArticle release];
}
for (OneOfTopArticle *t in listJsonTopArticle)
{
[[[[UIAlertView alloc] initWithTitle:@out
message:[NSString stringWithFormat:@%@",t.titreArticle]
delegate:nil
cancelButtonTitle:@OK
otherButtonTitles:nil] autorelease] show];
}
Merci d'avance pour votre aide.
Réponses
Le code est un peu bizarre, que renvoi splitJsonReceived ? un OneOfTopArticle alloué ?
Bonjour,
Oui, il y a une partie du code qui n'est pas listée. De plus:
1. count est un long;
2. pourquoi un release? Peux-tu transformer ton projet pour qu'il utilise ARC?
3. As-tu vérifié avec des NSLog ce que tu pensais être des valeurs correctes?
A+
Déjà , je t'avoue que j'ai du mal à lire ton code, alors je vais répondre à la deuxième question, je pense que ça va t'éclairer sur le reste.
Non. Je veux dire, le pointeur sur le NSMutableArray est une variable locale et a donc la portée d'une variable locale, mais un NSMutableArray ne disparait pas par magie de la mémoire, il faut lui envoyer un -[release] pour ce faire.
Ensuite, moi je vois un truc bizarre dans ton code:
Dans ta boucle, tu vas donc envoyer un -release i fois à myOneTopArticle, attends toi à un plantage.
Hello,
Alors plusieurs problèmes dans ton code :
1. Tu as un release qui n'a pas lieu d'être : si tu utilises ARC, les release sont proscrits. Si tu n'utilises pas ARC, un release doit forcément balancer un alloc, retain ou copy. Là tu as un objet myOneOfTopArticle que tu as alloué une fois (que fait la méthode "instance" ? Elle retourne un objet autoreleased, comme le laisse penser la convention de nommage si tant est que tu la respectes ?) et que tu release autant de fois que tu itères dans ta boucle !! Donc tu releases plusieurs fois d'affiler de la mémoire (crash assuré le jour où ton NSMutableArray relâche ses éléments et ne va plus retenir ton myOneOfTopArticle !!)
2. Ce qui ce passe c'est que tu utilises toujours le même objet, que tu ajoutes à ton NSMutableArray à chaque fois. Certes, tu changes ses propriétés, entre chaque itération de boucle avant de l'ajouter à nouveau, mais ça reste le même objet. Ajouter un objet à un NSMutableArray fait un retain sur cet objet (car le tableau va retenir l'objet), et non pas une copie. C'est pour ça qu'à la fin tu te retrouves avec le même objet partout dans ton tableau (et qui se trouve avoir pour valeurs dans ses propriétés... les dernières valeurs que tu as affecté aux propriétés de cet unique objet réutilisé à chaque fois).
Tu devrais ajouter une instance différente à chaque fois, puisque chaque élément de ton tableau devra être un élément différent à la fin.
En fait c'est un peu comme si tu voulais vendre 3 voitures à 3 personnes. Si en fait tu n'as qu'une voiture rouge, officialise la vente de cette voiture à Alice, puis repeint cette même voiture en vert ensuite pour faire une vente à Bob, puis repeint cette voiture en bleu pour la revendre à Cédric à la fin... Non seulement t'es un arnaqueur mais surtout au final ça restera la même voiture, qui sera bleue puisque c'est la dernière couleur dans laquelle tu l'as peinte, et quand Alice viendra chercher sa voiture elle sera bleue même si elle l'a achetée rouge. C'est la même instance que tu as réutilisé 3 fois, au lieu de vendre 3 voitures différentes.
Si tu voulais bien faire, il faudrait avoir géré 3 voitures réellement différentes, et vendre chaque voiture différente à une personne différente. Bah là c'est pareil, il faut créer une nouvelle instance d'un objet OneOfTopArticle à chaque fois (ou alors faire une copie du précédent, mais si c'est pour au final modifier ses propriétés, ça n'a aucun intérêt de faire une copie conforme pour tout changer, autant créer une instance toute neuve)
Bref, je te conseille de fortement réviser les concepts de gestion mémoire en Objective-C. Il y a un Programming Guide dédié pour ça, que je t'invite fortement à lire.
Bonjour,
Tout d'abord je tiens à vous remercier pour vos explication et vos questions, elles m'ont été d'un aide précieuse pour mieux comprendre mon erreur. Et désolé que la question fût si floue.
cyril94440 :
Ma fonction "splitJsonReceived" est la fonction de laquelle est extraite le code. C'est une fonction delegate avec une UIViewController. L'objectif de cette fonction est de prendre mon objet JSON, récupérer les données en les insuflant dans un objet "OneOfTopArticle" puis d'ajouter chaque article dans une liste. Ce qui est retourné à ma UIViewController est une liste de "OneOfTopArticle". Me permettant ainsi d'afficher tous ces objets dans une UITableView.
Céroce :
Etrangement je n'ai pas eu de plantage. Mais en retirant le release l'application était effectivement plus fluide.
berfis :
Je voulais ne pas surcharger ma mémoire en faisant à chaque fois un release sur mon OneOfTopArticle objet. Puis en réinstancier un objet pour l'ajouter à la liste. J'ai vérifié, les valeur des objets étaient correctes. C'était là mon problème... Lorsque je sortais de ma boucle for, comme l'a dit AliGator, je ne voyais que les valeurs du dernier objet ajouté.
AliGator :
Merci pour ton explication, elle m'a permis de comprendre où était mon erreur. Je t'avoue avoir des difficultés à comprendre ces histoires de "retain" et de gestion de mémoire... De quel livre parles-tu?
Voici le code fonctionnel :
Encore merci pour votre aide !
Les NSMutableArray sont des objets comme les autres.
En ce qui concerne la gestion de la mémoire de ces objets :
- si ils sont créés en cours de fonction, ils disparaissent à la fin de celle-ci, sauf si un retain ou un autorelease ont été ajoutés comme :
- si ils sont des objets de la classe, ils sont créés et effacés avec celle-ci. (déclaration dans le header, et initialisation dans la méthode "init" de la classe bien sûr...)
N'oublie pas aussi que cette classe est faite pour des objets Cocoa : elle n'accepte pas les types C (float, char...), il faut créer des NSNumber pour les y mettre.
Pas de retain...
Je ne parle pas d'un livre, mais de la documentation Apple : [url=https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html
]Memory Management Programming Guide[/url] entre autres.
Ceci dit la règle est simple :
1. Si tu n'utilises pas ARC (mais le MRR, Manual-Retain-Release), alors chaque alloc, retain ou copy ou mutableCopy dois être balancé par un release ou autorelease.
- Ne pas faire de release s'il n'y a pas de alloc/retain/copy avant (par exemple si tu fais NSString* s = [NSString stringWithFormat:@x=%d,x], alors ne pas faire [s release] car il n'y a jamais eu de alloc/retain/copy sur l'objet s) pour pas tenter de libérer de la mémoire qui ne correspond plus à rien.
- Et ne pas oublier de faire un release ou autorelease si tu as un alloc/retain/copy pour ne pas risquer sinon de fuites mémoires
Bref un alloc/retain/copy doit être balancé par release/autorelease, toujours, et seulement dans ce cas. Et ce balancement de (auto)release doit être fait en général dans la méthode qui a alloué/copié/retenu l'objet, pour pas que celui qui appelle la méthode ait à se poser la question de s'il doit relâcher des objets ou pas, c'est à celui qui crée l'objet de gérer sa destruction, pour plus de cohérence.
2. Si tu utilises ARC, tu n'as même plus besoin d'écrire de retain/release/autorelease, (tu n'as d'ailleurs même plus le droit), c'est le compilateur qui s'en chargera.
Après je te laisse lire les détails dans le Programming Guide qui contient bien + d'infos et des cas d'exemple et tout.
Merci pour ces précieuses explications.
Je vais rapidement me plonger dans le Programming Guide !
Salut a tous, discussion interessante d'autant que j'ai eu le meme probleme . J'ai été obligé d'ajouter un -retain a un NSArray.
Par contre, la ou je ne comprend pas, c'est que ce NSArray a été déclaré dans le header ET dans la methode d'initialisation de la classe. Donc normalement, pas besoin de -retain. Mais des que je l'enleve, j'ai un beau EXC_BAD_ACCESS
si ca vous interesse je met le code en dessous, il consiste juste a animer un cochon avec des PNG issues d'un NSArray:
(je me permet de continuer le topic car c'est exactement le meme probleme).
Bonne app les gars
Même problème même conseil : Va lire le programing guide sur la Gestion Mémoire...
Sinon, Ali a déjà tout dit... Il serait bien de lire les réponses des autres avant de poser ta question...
Bon c'est pas exactement le même problème mais ça en découle.
Ton soucis ici c'est que tu n'est pas responsable de l'objet vers lequel pointe t'as variable "mesImagesCochon", du coup l'objet s'autoreleasera.
Pour être responsable de l'objet tu dois passer par une de ces méthodes : alloc, copy, mutableCopy, retain. Dans ton example ça marche avec retain. Mais ça aurait pu tout aussi bien marcher par un alloc/init (et sans retain du coup).
merci pour ta reponse,
il semblerait que le probleme vient de l'autorelease. Je vais bosser ca plus en avant.
par contre :
-j'ai lu et compris les reponses precedentes : mon probleme n'est pas le meme mais en decoule (tu l'a dit toi meme)
-meme si j'ai lu les regles de memory management programming guide d'Apple, faut avouer que c'est tres concis et sec, faut du temps et des erreurs pour bien les approprier..
Bon en gros,
ça revient à faire exactement ça :
Le soucis ici c'est que tu assignes cet objet à une variable d'instance, qui aura une durée de vie importante. Donc tu te dois d'être responsable de cette durée de vie. Si tu as un objet qui est autorelease tu n'es pas responsable de cette durée de vie puisque c'est le système qui se chargera d'envoyer le message release un peu plus tard.
En appelant retain tu reprends la responsabilité de la durée de vie de cet objet, donc ça rétablie le fonctionnement.
Sauf que du coup ça revient à faire :
Donc autant simplifier et n'écrire que :
Car dans ce cas là , puisque tu as appelé toi même le alloc tu deviens responsable de ton objet : ce sera à toi, de contrebalancer cet alloc avec un release, comme l'explique Ali dans son post.
woah merci mille fois JegnuX, ca c'est de l'explication claire !
c'est la logique parfaite du developeur chevroné qui ressort
Donc autant simplifier franchement, et utiliser ARC et Objective-C 2.0, ce qui donne:
... ça me paraà®t plus lisible, et surtout ça marche comme sur du velours. ^_^
Il y en a qui ont plus d'une corde à leur ARC!!
@MAAT
C'est la boucle d'évènement qui détermine l'instant de l'autorelease (il me semble). J'ai eu aussi pas mal de problème avec ces aspects de persistance des données.
Pour être plus précis, c'est à la fin de chaque bloc @autoreleasepool { ... } que tous les objets ayant reçu un autorelease à l'intérieur de ce bloc @autoreleasepool { ... } sont releasés. En fait, envoyer un message autorelease à un message l'inscrit juste dans la liste des objets à releaser de l'@autoreleasepool{} de plus bas niveau (la dernière @autoreleasepool créée en somme) pour que dès que ce bloc @autoreleasepool se termine il envoie un vrai release à tous ces objets inscrits chez lui.
En pratique, dans 95% des cas (surtout pour un développeur débutant) on ne crée pas ou très rarement d'@autoreleasepool nous-même. Il y en a un de créé par défaut par le Runtime au niveau de la boucle d'évènements principale (la NSRunLoop du main thread). C'est pour cela qu'en effet dans 95% des cas, c'est à la fin de la boucle d'évènements que tous les autorelease précédemments envoyés vont réellement générer des release sur les objets correspondants comme tu l'as annoncé tablier.
Mais si jamais tu déclares une @autoreleasepool toi-même dans ton code, tout ce qui va être autoreleasé à l'intérieur sera réellement releasé à la fin de ton @autoreleasepool (sans attendre la fin de l'autoreleasepool de plus haut niveau qui est dans la NSRunLoop principale). Ce qui peut être très utile par exemple si tu fais une grosse boucle for avec bcp d'itérations et bcp de données/de gros objets créés à l'intérieur.