Gestion de la mémoire & communication entre ViewController

CubiCubi Membre
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 :
<br />@interface MyData : NSObject<br />{<br />&nbsp; &nbsp; &nbsp;  MyData *Suivant;<br />&nbsp; &nbsp; &nbsp;  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.

Réponses

  • DrakenDraken Membre
    juillet 2011 modifié #2
    Xcode 4.2 est en version GM et devrais bientôt être disponible pour le grand public. Intégrant ARC un mécanisme de gestion automatique de la mémoire, il vas simplifier considérablement la vie aux développeurs novices ! Je m'en sert actuellement pour développer pour iOS 4.2 et ça marche très bien !

    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/
  • laudemalaudema Membre
    juillet 2011 modifié #3
    Oui la solution de l'Ali-pédia est plus que valable dans ton cas de figure. Mais le problème de la mémoire étant récurrent tu ne perdras rien à  essayer de le résoudre avant d'adopter un meilleur schéma (protocole + delegate = plus souple et riche en adaptations futures).
    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.
  • CubiCubi Membre
    18:17 modifié #4
    Merci pour ces premières pistes.

    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 :

    @interface MyData : NSObject<br />{<br />&nbsp; &nbsp; &nbsp;  MyData *Suivant;<br />&nbsp; &nbsp; &nbsp;  NSString *fiche;<br />}<br />-(void) SetSuivant:(MyData*) NewNode:<br />@end<br /><br />@implementation MyData<br /><br />-(void) SetSuivant:(MyData*) NewNode<br />{<br />&nbsp; &nbsp; &nbsp; [NewNode retain];<br />&nbsp; &nbsp; &nbsp; [Suivant release];<br />&nbsp; &nbsp; &nbsp; Suivant = NewNode;<br /><br />@end<br /><br /><br />Ensuite dans un procédure récursive :<br /><br />-(void) Explore :(MyData*) Parent<br />{<br />&nbsp; &nbsp; MyData *New = [[MyData alloc] init:<br />&nbsp;  [Parent SetSuivant:New];<br />&nbsp;  [self Explore:New];<br />&nbsp;  [New release];<br />}
    


    Mais sans succès :/

    Je ne vois pas où est la faille dans la logique.

  • laudemalaudema Membre
    18:17 modifié #5
    Si tu appelles explore: avant d'avoir fait [new release] tu ne passeras dans [new release] qu'à  la fin de tous tes "explore" et il ne sera appelé que cette fois là , ne relâchant que le dernier ?
  • CubiCubi Membre
    juillet 2011 modifié #6
    Oui, l'exemple est volontairement dépouillé.

    La logique veut qu'on passe évidemment :

    <br /><br />-(int) Explore :(MyData*) Parent :(NSInteger*) N<br />{<br />&nbsp; &nbsp;  if ([N intvalue] &gt; 1)<br />&nbsp; &nbsp;  {<br />&nbsp; &nbsp; &nbsp; MyData *New = [[MyData alloc] init:<br />&nbsp; &nbsp;  [Parent SetSuivant:New];<br />&nbsp; &nbsp;  int Factoriel =N* [self Explore:New :N-1];<br />&nbsp; &nbsp;  [New SetN:Factoriel];<br />&nbsp; &nbsp;  [New release];<br />&nbsp; &nbsp;  return N;<br />&nbsp; &nbsp;  }<br />&nbsp; &nbsp;  else<br />&nbsp; &nbsp;  {<br />&nbsp; &nbsp;  return 1;<br />&nbsp; &nbsp;  }<br />}<br />
    


    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.
  • laudemalaudema Membre
    18:17 modifié #7
    Désolé le devoir m'appelle ailleurs, j'espère qu'un autre que moi pourra t'aider.
    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.
  • CubiCubi Membre
    juillet 2011 modifié #8
    Je réécris pour plus de lisibilité :

    -(int) Explore :(MyData*) parentData :(NSNumber*) n<br />{<br />&nbsp; &nbsp;  if ([n intvalue] &gt; 1)<br />&nbsp; &nbsp;  {<br />&nbsp; &nbsp; &nbsp; MyData *newData = [[MyData alloc] init:<br />&nbsp; &nbsp;  [parentData SetSuivant:newData];<br />&nbsp; &nbsp;  int factorielN =[n intvalue] * [self Explore:newData :[n intvalue] -1];<br />&nbsp; &nbsp;  [newData SetN:factorielN];<br />&nbsp; &nbsp;  [newData release];<br />&nbsp; &nbsp;  return factorielN;<br />&nbsp; &nbsp;  }<br />&nbsp; &nbsp;  else<br />&nbsp; &nbsp;  {<br />&nbsp; &nbsp;  return 1;<br />&nbsp; &nbsp;  }<br />}
    
  • CubiCubi Membre
    18:17 modifié #9

    J'ai aussi corrigé le NSInteger en NSNumber.
  • AliGatorAliGator Membre, Modérateur
    juillet 2011 modifié #10
    Heu déjà  sans regarder ta gestion mémoire :

    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 !!
  • CubiCubi Membre
    juillet 2011 modifié #11
    Merci pour ces informations.

    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 :
    <br />-(void) exploreData :(MyData*) parentData stringReturn:(NSString**)myStringReturn stringToParse:(NSString*)myStringSource<br />{<br />&nbsp; &nbsp;  if ([myString lenght] &gt; 1)<br />&nbsp; &nbsp;  {<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; MyData *newData = [[MyData alloc] init:<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [parentData setSuivant:newData];<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myString1 = [myStringSource stringTo:[myStringSource lenght]-1];<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; NSString *myStringReturn;<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [self exploreDara:newData&nbsp; stringReturn:&amp;myStringReturn stringToParse:myStringSource];<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [newData setString:myStringReturn];<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [newData release];<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return ;<br />&nbsp; &nbsp;  }<br />&nbsp; &nbsp;  else<br />&nbsp; &nbsp;  {<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return ;<br />&nbsp; &nbsp;  }<br />}<br />
    


    L'objectif est d'avoir pour la chaine "abc"

    /
    \                  /
    \                  /
    \                 
    |  Parent  |          /---> |  Child1  |        /---> |  Child2  |                 
    |
    |        |        |
    |        |        |
    |                 
    |  next
    /        |  next
    /        |  next
    >  nil
    |
    |                  |
    |                  |
    |                 
    |      a      |                  |      b      |                  |      c      |                 
    \
    /                  \
    /                  \
    /                 
  • Eric P.Eric P. Membre
    18:17 modifié #12
    dans 1311524668:

    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 ??
  • CubiCubi Membre
    18:17 modifié #13

    Hélà s, c'est le choix que j'ai fait hier.

    Mais je trouve ça dommage.
  • laudemalaudema Membre
    18:17 modifié #14
    Pourquoi t'embêter à  mettre une string par référence plutôt que de retourner un MyData en valeur de retour ?
    Quelque chose comme
    <br />- (MyData*)exploreData:(MyData*) parentData withString:(NSString*)zeString {<br />&nbsp; &nbsp; if ([zeString length] &lt;= 1)<br />&nbsp; &nbsp; &nbsp; &nbsp; return nil;<br />&nbsp; &nbsp; MyData *newData = [[[MyData alloc] init]autorelease];<br />&nbsp; &nbsp; newData.parent = parentData;<br />&nbsp; &nbsp; newData.string = zeString;<br />&nbsp; &nbsp; [self exploreData:newData withString:[zeString stringTo:[zeString lenght]-1];<br />}<br />
    

    ça doit pouvoir se faire autrement mais dans tous les cas je ne vois pas l'utilité de ta chaine par référence ?
  • CeetixCeetix Membre
    18:17 modifié #15
    Perso je comprends pas ton code.
    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.
  • CubiCubi Membre
    18:17 modifié #16

    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
Connectez-vous ou Inscrivez-vous pour répondre.