Gestion de la mémoire & communication entre ViewController
Je suis nouveau sur ce forum, aussi je me présente, Cubi, programmeur débutant en Cocoa Touch & Objective-C.
J'ai réalisé une appli à peu près fonctionnelle et maintenant que je m'attaque aux fuites ben ... j'en trouve
.
Je pense que j'ai commis la même erreur à plusieurs reprises mais je ne vois pas comment j'aurais du "bien faire".
Prenons un exemple :
J'ai 2 ViewController et je souhaite communiquer des données entre elles. J'utilise la NSNotification et cela fonctionne parfaitement, sauf que je suis obligé d'utiliser des "retain" à tour de bras pour éviter des structures de data vides avec des "no summary" dans le view de destination.
Du coup, après, je ne parviens pas à correctement libérer la mémoire et lorsque je réassigne la structure de données à d'autres variables, j'ai une fuite.
Pratiquement, ma question se résumé à :
imaginons que j'ai une classe MyData qui contient :
Comment initier correctement ces variables pour pouvoir passer un objet de cette classe au NSNotification ?
A l'arrivée de la notification, que devrais-je faire ? Recopier le contenu dans un objet "local" ?
Merci d'avance pour vos pistes de réflexions.
J'ai réalisé une appli à peu près fonctionnelle et maintenant que je m'attaque aux fuites ben ... j'en trouve

Je pense que j'ai commis la même erreur à plusieurs reprises mais je ne vois pas comment j'aurais du "bien faire".
Prenons un exemple :
J'ai 2 ViewController et je souhaite communiquer des données entre elles. J'utilise la NSNotification et cela fonctionne parfaitement, sauf que je suis obligé d'utiliser des "retain" à tour de bras pour éviter des structures de data vides avec des "no summary" dans le view de destination.
Du coup, après, je ne parviens pas à correctement libérer la mémoire et lorsque je réassigne la structure de données à d'autres variables, j'ai une fuite.
Pratiquement, ma question se résumé à :
imaginons que j'ai une classe MyData qui contient :
<br />@interface MyData : NSObject<br />{<br /> MyData *Suivant;<br /> NSString *fiche;<br />}<br />@end<br />
Comment initier correctement ces variables pour pouvoir passer un objet de cette classe au NSNotification ?
A l'arrivée de la notification, que devrais-je faire ? Recopier le contenu dans un objet "local" ?
Merci d'avance pour vos pistes de réflexions.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Pour ton problème de communication entre ViewController, regardes dans la section Tutoriel du Forum. Le groupe de développeurs communément appelé "collectif AliPédia" à écrit un article très intéressant sur le sujet.
http://ressources.mediabox.fr/tutoriaux/apple/protocol
Sinon, la présentation des nouveaux c'est ici :
http://pommedev.mediabox.fr/presentation-des-membres/
Pour revenir à la mémoire tu passes normalement des objet dans une NSNotification à l'aide d'un NSDictionary (userInfo).
Pendant le transport c'est le dictionnaire qui a la propriété de l'objet. Dès que tu l'as mis dans le dictionnaire tu peux le détruire si tu n'en as plus besoin dans ce premier objet qui l'a créé.
Quand tu sors de la méthode qui a reçu le dictionnaire celui ci est effacé et le retain-count des objets contenus est décrémenté. Si l'objet transporté a été détruit entre deux dans la classe qui l'avait initialisé alors tu n'as plus rien qu'un "dangling pointer" et une source de crash imminent.
Pour ne pas perdre cet objet, tant que tu en as besoin tu peux soit le copier, soit utiliser [tt]retain[/tt] pour augmenter son [tt][retain count][/tt] et, dans les deux cas, utiliser [tt]release[/tt] quand tu n'en a plus besoin.
En attendant que tout le monde soit passé à Lion et qu'on puisse utiliser ARC partout tu peux te concentrer sur les règles de mémoire Apple qu'on se sent toujours obligé de rappeler dans ces cas là : Apple Memory Management Rules.
Effectivement, j'en étais arrivé à la même conclusion.
J'avais d'ailleurs refait le point sur mes connaissances "mémoire" après avoir posté car mon debugging me montrait que le souci était beaucoup plus général.
Maintenant je me rends compte que c'est même dans un ViewController que le problème se pose.
Le problème face auquel je suis confronté est que les exemples simples sont facile à comprendre mais dans mon cas, d'initialisation récursive, je me perds un peu.
J'ai essayé ce genre de chose :
Mais sans succès
Je ne vois pas où est la faille dans la logique.
La logique veut qu'on passe évidemment :
L'objectif étant d'avoir dans la première structure MyData, la valeur totale, dans [MyData Next], la factorielle de N-1, et ainsi de suite de Next en Next.
Là je suis aussi un peu perdu : un pointeur sur un NSInteger ? ? ? (et alors N-1 c'est une typo ?)
Aussi, une recommandation pour la suite : les variables commencent avec une minuscules c'est une convention et comme elle est largement adoptée on a tendance à voir tout ce qui commence par une majuscule pour une Classe.
J'ai aussi corrigé le NSInteger en NSNumber.
1) Tu ne respectes pas les conventions de nommage, ce qui rend la lecture et compréhension de ton code plutôt difficile.
Les noms des méthodes sont à écrire en "lower camelCase", c'est à dire commençant par une minuscule (et avec une majuscule au début de chaque mot). Par exemple "explore:" ou "exploreData:" ou "exploreData:parent:". Les variables quant à elles commencent par une minuscule. Seules les classes (et les constantes) commencent par une majuscule.
2) Pense à donner des noms à tes paramètres, je n'ai jamais vu une méthode dont les paramètres ne sont pas nommés. C'est à dire qu'il faut prévoir un préfixe devant tes ":". Là dans ton exemple de code ta méthode est écrite "-(int) Explore :(MyData*) Parent :(NSInteger*) N" autrement dit la méthode s'appelle "Explore::" et le premier paramètre, de type MyData*, es associé à la variable "Parent", et le 2e paramètre, de type NSInteger*, est associé à la variable "N".
Tu voulais plutôt sûrement mettre : "-(int) Explore :(MyData*)data Parent :(NSInteger*) N" où là le premier paramètre est associé à la variable "data", le second à la variable "N", et la méthode aurait pour signature "Explore:Parent:"
3) Dans tous les cas, ta signature de méthode est très étrange. Tu passes un NSInteger*, alors que NSInteger est un type atomique C (en fait c'est un "long int" pour faire simple), donc tu passes un pointeur vers un entier... et en plus tu manipules ce dernier comme si c'était directement l'entier, sans le déréférencer... Et je ne vois aucun intérêt à le passer par pointeur au lieu de passer sa valeur directement. Il n'y a que pour les types objets (dérivant de NSObject typiquement) que l'on manipule ces derniers par pointeur en Objective-C.
[EDIT]Je vois que tu as corrigé ça, c'est un bon début... cependant quel intérêt de manipuler des objets NSNumber* là où un NSInteger normal suffit ? L'Objective-C c'est bien, mais rajouter une encapsulation objet autour d'un simple nombre quand ce n'est pas nécessaire ce n'est pas non plus forcément une bonne idée, ce n'est utile que quand tu as besoin de les manipuler justement sous forme d'objet (pour les mettre dans un conteneur comme NSArray ou NSDictionary ou pour le KVC etc) mais sinon autant éviter quand ce n'est pas justifié
4) En plus, tu n'indentes pas ton code, ce qui n'aide pas non plus à sa lecture.
Au final, rien que ta signature de méthode comporte 3 erreurs ! Et en plus elle n'est pas explicite.
- Il aurait fallu donc écrire : [tt]-(int)exploreData:(MyData*)data withParent:(NSInteger)parent[/tt], donc avec le respect des conventions de nommage et des majuscules/minuscules, le nommage des paramètres, et le bon type de params
- Mais en plus à se demander à quoi sert le "data" (enfin l'objet MyData) que tu passes à ta méthode, puisque tu ne l'utilises jamais (et que tu crées un nouveau MyData pour appeler ta méthode réccursivement... nouveau MyData qui ne sert toujours à rien)
---
Rien qu'en faisant ces corrections là , et en te demandant à quoi te servent tes MyData dans ton code, ton code sera beaucoup plus propre. Et surtout plus lisible donc plus facilement déboguable.
Après pour les algos reccursifs je ne conseillerai pas forcément l'attaque en Objective-C (pour ce qui est algorithmie réentrante, le C est parfois suffisant, ça dépend des contextes mais bon quand y'a pas de raison de se trimbaler des objets comme pour le calcul de la factorielle...), mais même si tu veux le faire, en effet il faudra gérer cela mieux que ça car là tu as un pic de mémoire le temps que ton algo arrive à la condition d'arrêt (return 1), et seulement après ça remonte les appels de méthode reccursifs pour faire tous les release, uniquement à la fin de l'algo. Loin d'être optimal.
La bonne question à se poser c'est surtout pourquoi tu as à recréer un MyData à chaque itération de ton code, et donc à chaque fois que tu fais un appel réccursif à ta méthode ? Pour une grosse factorielle ça risque d'être très très coûteux, tant en mémoire qu'en temps d'exécution !!
Je n'ai pas copier/coller mon code car je trouve ça toujours très difficile à lire. Ma fonction Explore parse en fait une chaine de caractère et elle renvoie les morceaux de chaines à 1 ou plusieurs autres fonctions Explore.
Le résultat est envoyé à une View pour y être affichée dans un scrollview sous forme d'arbre N-aire.
L'utilisateur clique sur un noeud de l'arbre et les informations présentent dans le noeud s'affichent.
Comme ce sont des strings, je me suis dit autant utiliser des NSString. C'est pour ça que j'ai réécris le code "en version simplifiée".
La signature est bien : exploreData:MyData*:NSString*
Je réécris mon code :
L'objectif est d'avoir pour la chaine "abc"
/
\ /
\ /
\
| Parent | /---> | Child1 | /---> | Child2 |
|
| | |
| | |
|
| next
/ | next
/ | next
> nil
|
| |
| |
|
| a | | b | | c |
\
/ \
/ \
/
Bonjour,
Cela ne serait pas plus simple avec un NSMutableArray ??
Hélà s, c'est le choix que j'ai fait hier.
Mais je trouve ça dommage.
Quelque chose comme
ça doit pouvoir se faire autrement mais dans tous les cas je ne vois pas l'utilité de ta chaine par référence ?
Pour quoi tu utilises le double pointeur sur ton NSString en paramètre de méthode et surtout pourquoi tu re-déclares un NSString * avec le même nom que ton paramètre ?
Ton algo est faux, même si tu passes dans la première condition, jamais tu n'atteindras la manipulation de newData.
En fait les pointeurs de pointeurs sont la pour récupérer les valeurs du niveau n-1.
Le programme parse un string de character et compute plusieurs autres strings qui sont récupérées par le niveau supérieur et agrégées entre elles.
Je rappelle que le code était fonctionnel, mais relativement complexe. Mon problème est "juste" un problème de gestion de la mémoire, raison pour laquelle j'ai tenter d'isolé ce problème de gestion de la mémoire en donnant des exemples simples.
Il peut se résumer en gros, à : comment créer un objet de type de sa propre classe dans sa classe - et surtout - comment l'allouer et le "désallouer" proprement .
Cubi