Problème avec NSMutableString

FreggFregg Membre
20:03 modifié dans API AppKit #1
Bonjour à  tous,

J'ai, pour la première fois, utilisé une NSMutableSting. Malheureusement je n'ai, semble-t-il, pas du comprendre comment cela fonctionnait.
<br />NSMutableString * userData = [[NSMutableString alloc] initWithCapacity:150];<br />id anObject;<br />int i = 0;<br />int j = 0;<br /><br />i = [self numberOfFrameNamed:@&quot;TXXX&quot;]; // recupère le nombre d&#39;occurence de la chaine TXXX<br />for(j; j&lt;i; ++j) { // pour j allant de 0 à  i<br />	anObject = [self getFrameNamed:@&quot;TXXX&quot; atIndex:j];&nbsp; // on chope le text de la jème occurence<br />	NSLog([anObject getTextFromFrame]); // affichage console de la jeme occurence<br />	[userData appendString:[anObject getTextFromFrame]]; // save de la jème occurence dans userData<br />}<br />[userData autorelease];<br />UserData = userData;<br />NSLog(@&quot;&#092;n&quot;);<br />NSLog(UserData);&nbsp; &nbsp; &nbsp;// affichage de TOUTES les strings sauvées<br />


et voici ce que la console m'affiche :

Rip date
Source
Ripping tool
Release type

Rip date


L'erreur se situe au second 'Rip date', vu qu'il devrait m'afficher les 4 en suivant et qu'il ne m'en affiche qu'un seul des 4...

Je suis aller voir la documentation de la class NSMutableString, mais j'ai rien vu qui m'aide. J'ai chercher également sur Google, mais rien non plus qui me mette sur la voie...

Quelqu'un aurait-il une idée ? Ais-je encore fait une erreur toute bête ?

Merci beaucoup d'avance pour votre aide et votre patience...
«1

Réponses

  • BruBru Membre
    janvier 2008 modifié #2
    Dernière ligne de ton code :

    Et si tu écrivais plutôt ça : NSLog(@%@", UserData); // affichage de TOUTES les strings sauvées,
    ça donnerai quoi à  la console ?


    PS, je ne comprends pas trop non plus la ligne UserData = userData;... C'est pas que ce soit faux, mais j'espère que Userdata n'est pas une variable d'instance...

    .
  • FreggFregg Membre
    20:03 modifié #3
    dans 1200926983:

    Dernière ligne de ton code :

    Et si tu écrivais plutôt ça : NSLog(@%@", UserData); // affichage de TOUTES les strings sauvées,
    ça donnerai quoi à  la console ?

    Cela me donne exactement la même chose...

    dans 1200926983:

    PS, je ne comprends pas trop non plus la ligne UserData = userData;... C'est pas que ce soit faux, mais j'espère que Userdata n'est pas une variable d'instance...


    Je suis pas sure de savoir ce qu'est une "variable d'instance"... L'OOP est tout nouveau pour moi... UserData est un membre d'un objet de ma classe (je crois que c'est comme cela qu'on dit)... mon but est de récupérer toutes les instances des chaines "TXXX" et de les mettre dans une seule variable. Pour cela j'ai, dans mon objet, un champ UserData (U MAJUSCULE) dans lequel je voudrais sauvegarder ma variable NSMutableString temporaire userData (u minuscule). Ce champs UserData est lui aussi de type NSMutableString...

    Pourquoi espères-tu que cela ne soit pas une variable d'instance ? Que cela ferait-il si c'était le cas ?

    Merci de ton aide...
  • psychoh13psychoh13 Mothership Developer Membre
    20:03 modifié #4
    Déjà  une variable d'instance c'est ce que toi tu as appelé un "champ" :
    @interface MaClasse : NSObject {<br />&nbsp; &nbsp; // variables d&#39;instance<br />}<br />// méthodes<br />@end
    


    En général, les variables d'instances ne prennent pas de majuscules à  la première lettre du nom. Ce sont les classes et les types qui ont un nom commençant par une majuscules.

    Enfin peu importe, moi ce que j'aimerais savoir ce sont plusieurs choses :
    • Pourquoi tu n'utilises pas directement la variable d'instance au lieu de passer par ta variable temporaire ?
    • Pourquoi ne t'assures-tu pas que l'ancienne valeur de la variable d'instance est supprimée avant de faire la nouvelle affectation ?
    • Pourquoi tu envoies le message "autorelease" à  ton objet temporaire avant de l'affecter à  ta variable d'instance ?


    Sur ce dernier point, faire ce que tu fais signifie que ta NSMutableString sera désallouée à  la sortie de ta méthode... Et donc, ta variable d'instance contiendra une référence vers un objet... Mort... Je doute que ce soit ce que tu veux.
  • FreggFregg Membre
    janvier 2008 modifié #5
    dans 1200928854:

    Déjà  une variable d'instance c'est ce que toi tu as appelé un "champ" :
    @interface MaClasse : NSObject {<br />&nbsp; &nbsp; // variables d&#39;instance<br />}<br />// méthodes<br />@end
    


    En général, les variables d'instances ne prennent pas de majuscules à  la première lettre du nom. Ce sont les classes et les types qui ont un nom commençant par une majuscules.


    Oki, donc, c'est une variable d'instance... Ok, je changerai les majuscule...

    dans 1200928854:

    • Pourquoi tu n'utilises pas directement la variable d'instance au lieu de passer par ta variable temporaire ?



    • Parce que cela me semblait plus propre d'utiliser un variable temporaire pui s de la "déversée" dans ma variable d'instance (ai appris un nouveau mot !  :-*). Et parce que quand je veux modifier directement ma variabel d'instance j'ai un message d'erreur dans la console :" Attempt to mutate immutable object with appendString: " (alors que ma variable d'instance est bien une NSMutableString *)



    dans 1200928854:

    • Pourquoi ne t'assures-tu pas que l'ancienne valeur de la variable d'instance est supprimée avant de faire la nouvelle affectation ?


    • Heu... je ne sais pas... Je devrais ? Parce que c'est à  dire que pour le moment je voudrais qu'il y ai plusieurs choses dedans...



    dans 1200928854:

    • Pourquoi tu envoies le message "autorelease" à  ton objet temporaire avant de l'affecter à  ta variable d'instance ?


    • C'est ce que j'avais vu dans différent code d'exemple, mais je dois avouer que je ne comprennais pas bien ce que faisait l' "autorelease"... Maintenant que je sais, je ne fais plus...
  • psychoh13psychoh13 Mothership Developer Membre
    20:03 modifié #6
    dans 1200930138:
    • Parce que cela me semblait plus propre d'utiliser un variable temporaire pui s de la "déversée" dans ma variable d'instance (ai appris un nouveau mot !  :-*). Et parce que quand je veux modifier directement ma variabel d'instance j'ai un message d'erreur dans la console :" Attempt to mutate immutable object with appendString: " (alors que ma variable d'instance est bien une NSMutableString *)
    • Heu... je ne sais pas... Je devrais ? Parce que c'est à  dire que pour le moment je voudrais qu'il y ai plusieurs choses dedans...


    Si tu as ce message d'instance ça veut dire que ta variable d'instance N'est PAS modifiable... De plus, si tu fais un simple =, tu ne déverses rien du tout dans la variable, tu remplaces la référence directement, donc tu perds ce qu'il y avait avant...

    Donc pour le message d'erreur tu as du te tromper dans ta première affectation... Ce n'est pas parce que tu déclares une variable comme étant une NSMutableString que ce sera forcément ce type-là  à  l'exécution... Au contraire... Tu empêches juste le compilateur de voir que tu fais une connerie. :D
  • FreggFregg Membre
    janvier 2008 modifié #7
    voici ma variable d'instance :

    NSMutableString * userData;


    Je ne comprends pas du coup pq elle n'est pas modifiable.

    dans 1200930671:

    Donc pour le message d'erreur tu as du te tromper dans ta première affectation... Ce n'est pas parce que tu déclares une variable comme étant une NSMutableString que ce sera forcément ce type-là  à  l'exécution... Au contraire... Tu empêches juste le compilateur de voir que tu fais une connerie. :D

    Je ne comprends pas trop cela... Je suis un vrai débutant, tant en objective-c/cocoa qu'en OOP... Et je suis désolé, mais je ne crois pas comprendre le concepte sous-jacent de ton discourt...

    A propos de pointeur, j'ai remarquer que - dans mon objet - j'ai des bool, des int, mais des que j'ai des NSString, j'utilise que des (NSSting *). Ne vaudrait-il pas mieux utiliser des NSString, histoire de prendre moins de place mémoire puisqu'au lieu de stocker une adresse ET un string, il ne stockerait qu'un string. Non ?
  • psychoh13psychoh13 Mothership Developer Membre
    20:03 modifié #8
    Tu veux dire écrire ceci :

    @interface MaClasse : NSObject<br />{<br />&nbsp; &nbsp; NSString maString;<br />}<br />@end
    

    ???

    Si, c'est ce que tu penses alors non il ne faut pas faire ça et de toutes façons ça ne devrait pas marcher.
    En Objective-C (de même qu'en Java et C#), TOUS les objets sans exception sont TOUJOURS alloués DYNAMIQUEMENT c'est-à -dire sur le tas, et ce que l'on manipule c'est leurs adresses, on transmet une adresse à  une méthode, on reçoit une adresse d'une autre, etc. Et l'adresse nous permet d'accéder au contenu de l'objet, on appelle d'ailleurs plus ça une "adresse", on appelle ça une référence.
    Pourquoi fait-on ça ?
    Pour plusieurs raisons, pour commencer pour des raisons de place. Une variable que l'on déclare comme ceci dans une méthode :
    int maVariable;
    


    Cette variable est allouée sur la pile de la méthode, la pile est une mémoire très limitée dans le temps et dans sa capacité. Lorsque l'on transmet une variable en paramètre à  une méthode, sa valeur est copiée sur la pile de la méthode, ce qui fait que lorsqu'on modifie la variable dans la méthode, la variable transmise lors de l'appel n'est, elle, pas modifiée.
    Pourquoi ne fait-on pas ça avec les objets ? Bah justement parce qu'on veut qu'une méthode modifie l'objet transmis, mais aussi parce que la place sur la pile n'est pas suffisamment grande pour stocker des objets de grande taille comme des NSString de quelques milliers de caractères. Stocker un pointeur de 4 octets (ou 8 en 64 bits) et bien plus économique que de stocker tout un objet relativement volumineux.

    Une autre raison, c'est le dynamisme, si tu as été curieux, alors tu as regardé le Class Browser (dans le menu Project, cmd + shift + C) et tu as regardé, grâce à  ça, les fichiers d'en-tête de toutes les classes intégrées dans ton projet et notamment les classes de Cocoa.
    Si tu ne l'as pas fait, fais-le, tu seras surpris de voir que beaucoup de classes et notamment NSString ne définissent aucune variable d'instance. Comment stocke-t-on les caractères alors ?
    Et bien en fait, lorsqu'on manipule des NSString, on ne manipule jamais des instances de NSString mais des instances des sous-classes privées, NSString n'est qu'une interface commune à  toutes les sous-classes. Quand tu vas utiliser la méthode : [[NSString alloc] init] pour créer une chaà®ne de caractères vide, NSString va en fait d'abord retourner une instance statique d'une sous-classe temporaire nommé NSPlaceholderString, et lorsque tu vas envoyer le message "init" à  cet objet, tu vas en fait récupérer une nouvelle instance d'une différente sous-classe, cette fois-ci il s'agit de NSCFString. Et ce sont en fait des objets de type NSCFString que tu manipuleras et non pas des NSString.

    Le compilateur lui est incapable de savoir ça, et donc il est incapable de définir l'allocation d'espace correcte pour tes instances.

    Et pour finir, une autre raison c'est que tu peux, grâce aux adresses (références), partager 1 unique objet (de 100 ko par exemple) entre plusieurs instances au lieu de copier systématiquement les 100 ko pour chaque objet, et rendre les modifications de chaque objet très difficiles.

    dans 1200942020:

    voici ma variable d'instance :

    NSMutableString * userData;


    Je ne comprends pas du coup pq elle n'est pas modifiable.


    Comme je l'ai dit plus haut, le fait d'écrire ce code dit juste au COMPILATEUR et uniquement à  lui, que la variable est de type "pointeur sur NSMutableDictionary", on appelle ça du typage statique, c'est-à -dire que le type est connu à  la compilation et non pas à  l'exécution comme c'est le cas du typage dynamique.
    Mais, en Objective-C, le typage statique est un nuage de fumé qui permet juste au compilateur de pas faire de grosses bourdes quand il interprète les messages. Mais en fait, le typage en Objective-C reste totalement dynamique. C'est-à -dire, que tu peux écrire ce genre de truc :

    Ce code ne générera ni avertissement, ni erreur lors de la compilation, mais cela plantera lors de l'exécution, parce que tu crées une NSString et non pas une NSMutableString, ce que le compilateur ne voit pas.
    NSMutableString *maString = [NSString string];<br />[maString appendString:autreString];
    


    En revanche, ce code générera un avertissement à  la compilation mais marchera très bien lors de l'exécution :

    NSString *maString = [NSMutableString string];<br />[maString appendString:autreString];
    


    Parce qu'ici le compilateur croira que tu essayes d'envoyer le message -appendString: à  une NSString, la NSString ne répondant pas à  ce message, ça te donne un avertissement. Mais lors de l'exécution, tu affectes une NSMutableString à  maString, et il se trouve que cet objet répond au message -appendString: donc ça marche sans problème.
  • FreggFregg Membre
    janvier 2008 modifié #9
    Waouw... Merci beaucoup pour cette explication plus que complète me semble-t-il... J'ai tout lu, mais pas encore tout compris. Mais je relirai, c'est promis...

    Ceci étant, j'ai compris que je garderai mes pointeurs...
    Par contre, concernant la variable d'instance userData, j'ai pas encore tout tout compris... D'autant plus que je la déclare en tant que NSMutableString *, et je l'égale a un NSMutableString*. Je ne comprends donc pas pq cela ne fonctionne pas...  :crackboom:-
    <br /> 1. -(void) setUserData<br /> 2. {<br /> 3. 	NSMutableString * tempString = [[NSMutableString alloc] initWithCapacity:150];<br /> 4. 	id anObject;<br /> 5. 	int i = 0;<br /> 6. 	int j = 0;<br /> 7. 	<br /> 8. 	i = [self numberOfFrameNamed:@&quot;TXXX&quot;];<br /> 9. 	for(j; j&lt;i; ++j) {<br />10. 		anObject = [self getFrameNamed:@&quot;TXXX&quot; atIndex:j];<br />11. 		NSLog([anObject getTextFromFrame]);<br />12. 		[tempString appendString:[anObject getTextFromFrame]];<br />13. 	}<br />14. 	userData = tempString;<br />15. 	NSLog(userData);<br />16. }<br />
    


  • psychoh13psychoh13 Mothership Developer Membre
    20:03 modifié #10
    Ce que j'avais compris c'est que tu avais essayé de modifier ta variable "UserData" en utilisant appendString: dessus mais que ça n'avait pas marché et c'est pour tu avais décidé d'utiliser une variable temporaire...

    Alors ma question maintenant c'est : est-ce qu'en modifiant userData avec appendString: as-tu toujours le même problème ? Je dis bien "modifier" et non pas "remplacer" car userData = tempData; ça remplace l'ancienne référence de userData par la référence contenue dans tempData...
  • FreggFregg Membre
    janvier 2008 modifié #11
    En remplaçant, à  la ligne 12. de mon post précédent "tempString" par "userData", j'ai le message suivant :
    Attempt to mutate immutable object with appendString:


    En laissant "tempString" j'ai le bug suivant :

    Rip date
    Source
    Supplier
    Ripping tool
    Release type
    Rip date // <<== c'est ici que se situe le prob

    Il me répete donc deux fois "Rip date" plutot que de m'afficher les 5 valeur en une seule fois... :s

    Edit : D'après les test que je viens de faire, en fait ce qu'il se passe, c'est que le "appendString" ne fonctionne que la première fois :

    i = [self numberOfFrameNamed:@TXXX];
    for(j; j<i; ++j) {
    anObject = [self getFrameNamed:@TXXX atIndex:j];
    NSLog([anObject getTextFromFrame]);
    [tempString appendString:[anObject getTextFromFrame]];
    NSLog(tempString);
    }


    ce qui le fait afficher :

    Rip date
    Rip date
    Source      // <== jusque la ca va
    Rip date    // <== la ca va plus...
    Ripping tool
    Rip date
    Release type
    Rip date


  • AliGatorAliGator Membre, Modérateur
    20:03 modifié #12
    My two cents pour les petits conseils généraux :

    1) Alors un petit conseil de bonne habitude pour débuter la POO, le nommage des variables.
    Certes tu étais parti d'une bonne intention puisque tu avais mis un nom différent pour ta variables d'instance "UserData" et ta variable locale "userData" dans ta fonction...
    Manque de pot, même si rien ne t'y oblige, la convention veux qu'on utilise en général une majuscule en début de nom pour le nom des classes. Donc raté, c'était une bonne idée mais pas le bon distingo :D
    Perso je te conseille de faire commencer par exemple le nom de toutes tes variables d'instance par un "_", par exemple "_userData". "userData" sera une variable locale (dont la durée de vie n'ira pas plus loin qu'une fonction/méthode), alors que "UserData" serait plutôt le nom d'une classe et USERDATA le nom d'une constante.


    2) Sinon, je te conseille aussi de lire ou te renseigner sur la gestion de la mémoire en Cocoa : ce n'est pas un sujet bien compliqué quand on l'a abordé, mais il est primordial et t'évitera bien des erreurs comme celles que tu commets dans ton code. C'est la base à  bien connaà®tre avant de se lancer plus loin dans Cocoa à  mon avis.

    Comme te l'a un peu expliqué psychoh13, tu ne manipules en Cocoa généralement que des pointeurs (c'est la signification de la petite étoile "*" derrière le "NSMutableString" par exemple). Ce qui fait que quand tu fais [tt]_userData = userData[/tt] tu ne fais pas grand chose en fait : tu ne fais que copier la même référence dans les 2 variables, en somme. Mais si l'objet "userData" vient à  ne plus exister, alors _userData non plus ne pointera sur rien.

    C'est justement dans ce cas que servent les alloc/release/autorelease, pour gérer la mémoire comme il faut, et dire à  ton programme "garde moi cette variable de côté j'en ai besoin à  d'autres endroits. (Je te laisse lire les tutos que tu peux trouver sur le net, ils t'expliqueront ça mieux que moi).
    D'ailleurs je me demande si c'est pas ta non-gestion de la mémoire qui pourrait être à  l'origine de tes bugs.

    Voilà , bonne suite :)
  • psychoh13psychoh13 Mothership Developer Membre
    20:03 modifié #13
    Bon maintenant pour régler ton problème, il nous faudrait plus de précision...

    Voilà  ce que je voudrais savoir, est-ce que les données que tu récupères grâce à  cette méthode te sont utiles au-delà  de la méthode qui l'appelle ?

    Voilà  un petit exemple pour rendre les choses :

    - (void)maMethode<br />{<br />&nbsp; &nbsp; // modification des données de l&#39;utilisateur<br />&nbsp; &nbsp; [self setUserData];<br /><br />&nbsp; &nbsp; // manipulation des données...<br /><br />&nbsp; &nbsp; // fin de la méthode<br />}
    


    Ce que je voudrais donc savoir c'est si tu as une méthode comme celle-ci qui appelle systématiquement -setUserData, et est-ce que les données renouvelées servent à  une autre méthode que celle-ci ?

    Parce qu'en fait ce que je vois là  c'est que tu fais un "cache", tu stockes les données renouvelées pour qu'elles servent à  d'autres méthodes voire d'autres objets qui n'appellent pas forcément -setUserData. Le problème étant que tu stockes peut-être des informations en croyant bien faire, alors que tu ne le devrais.
    Si ces données doivent être renouvelées à  chaque fois qu'on le demande, alors stocker tout ça dans une variable d'instance est inutile, il vaut mieux à  ce moment simplement retourner une référence de l'objet créé par cette méthode.



    PS : Mettre un "_" devant les noms des variables d'instance n'est pas une pratique recommandée. Apple, pour commencer, réserve ce préfix pour son propre usage, mais aussi ce préfixe est utilisé pour les variables d'instance privées et non pas protégées.
  • FreggFregg Membre
    janvier 2008 modifié #14
    Merci pour tes conseils AliGator... J'ai déjà  remplacer tout les majuscule dans ma variable d'instance et mes méthodes et j'en ai mises à  mes classes (et j'ai modifier le code en conséquence).

    Et je vais de ce pas tâcher de me renseigner sur la gestion de la mémoire...

    Ceci étant j'ai, a priori, du mal a voir le lien entre la gestion de la mémoire et mon soucis... Ce qui prouve que j'ai énoooooooooooooooooooooooooooooooormément des progrès a faire... C'est en même temps très stimulant et décourageant...

    Pour répondre a ta question psychoh13, ma méthode ne sert qu'a aller chercher des infos dans un fichier et a stocker dans un objet. Une fois ma méthode finie, ma variable d'instance "userData" est remplie et c'est tout, je n'y touche plus (ni modif, ni reinit).
    Après, je n'accède à  cette variable d'instance (peut-être plusieurs fois) que par une autre méthode "getUserData" pour qu'elle me renvoie la valeur contenue dans mon champ "userData" (accès lecture uniquement donc)...

    En gros, je fais une fois appelle a "setUserData" et plusieurs fois appelle a "getUserData" par fichier choisi par l'utilisateur.

    Cela répond-il a ta question ?

    *** EDIT : J'ai mis "userData" dans un NSTextView dans ma GUI et la, ôh surprise, il affiche toute l'info qu'il semble avoir récupéré correctement. Je suis on ne peut plus content du coup, car mon "problème" est résolu...

    Ceci étant, l'affichage console de "userData" n'a pas changé lui et ne correspond pas a ce que ma GUI affiche... Or on sait maintenant que malgré ce qui est affiché dans la console, il y a bcp plus en vrai... Comment cela est-il possible...  :crackboom:- :crackboom:- :crackboom:-
  • schlumschlum Membre
    janvier 2008 modifié #15
    Concernant la gestion de la mémoire, je te conseille "Cocoa par la pratique" d'Aaron Hillegass

    Pour tes variables d'instance qui sont des pointeurs d'instance, il faut que tu définisses un setter :

    - (void)setUserData:(NSMutableString*)data<br />{<br />    [data retain];<br />    [userData release];<br />    userData = data;<br />}
    


    Ensuite, tu utilises ce setter pour changer ta variable d'instance.
       
  • psychoh13psychoh13 Mothership Developer Membre
    20:03 modifié #16
    Tu t'es trompé sur la dernière ligne de code... Un égal dans un message ? :D

    Sinon il vaut mieux écrire ça comme ça :

    - (void)setUserData:(NSMutableString *)data<br />{<br />&nbsp; &nbsp; if(userData != data)<br />&nbsp; &nbsp; {<br />&nbsp; &nbsp; &nbsp; &nbsp; [userData release];<br />&nbsp; &nbsp; &nbsp; &nbsp; userData = [data retain];<br />&nbsp; &nbsp; }<br />}
    


    Sinon, c'est sûr que le nom de sa méthode est mal choisi. Je l'aurais plutôt appelé "loadUserData". Sinon, j'ai peur que pour régler ton problème il nous manque un peu de code...
  • schlumschlum Membre
    janvier 2008 modifié #17
    Effectivement, des crochets en trop...

    Par contre, non, il ne vaut pas mieux.
    Faire un test n'est bénéfique que lorsque l'objet donné est égal à  celui qui était là  avant ; c'est à  dire... presque jamais.

    Donc avec cette technique, tu fais à  tous les coups un test qui n'est quasiment jamais bénéfique, alors qu'en utilisant celle que j'ai donné, on fait fait un +1 -1 sur le retainCount  dans le cas où c'est la même (donc quasiment jamais et pas dramatique non plus, même si ça arrive de temps en temps).


    Bon, après dans la pratique, c'est du pinaillage ; il n'y a pas réellement une méthode qui est meilleure que l'autre, ce genre d'opérations c'est peanuts par rapport à  celles qui sont faites avant ou après.
  • FreggFregg Membre
    20:03 modifié #18
    voici le code complet de ma méthode (qui fonctionne) :
    <br />-(void) setUserData<br />{<br />&nbsp; &nbsp; NSMutableString * tempString = [[NSMutableString alloc] initWithCapacity:150];<br />	id anObject;<br />	int i = 0;<br />	int j = 0;<br /><br />&nbsp; &nbsp; if (present==YES) {<br />&nbsp; &nbsp; &nbsp; &nbsp; if(majorVersion &lt; 3) {<br />			i = [self numberOfFrameNamed:@&quot;TXX&quot;];<br />			for(j; j&lt;i; ++j) {<br />				anObject = [self getFrameNamed:@&quot;TXX&quot; atIndex:j];<br />				[tempString appendString:[anObject getTextFromFrame]];<br />				[tempString appendString:@&quot;&#092;n&quot;];<br />			}<br />&nbsp; &nbsp; &nbsp; &nbsp; } else if ((majorVersion == 3) || (majorVersion == 4)) {<br />			i = [self numberOfFrameNamed:@&quot;TXXX&quot;];<br />			for(j; j&lt;i; ++j) {<br />				anObject = [self getFrameNamed:@&quot;TXXX&quot; atIndex:j];<br />				[tempString appendString:[anObject getTextFromFrame]];<br />				[tempString appendString:@&quot;&#092;n&quot;];<br />			}<br />&nbsp; &nbsp; &nbsp; &nbsp; }<br />	}<br />	userData = tempString;<br />}<br />
    


    Mon problème est reglé, tout s'affiche bien dans la GUI. La seule petite interrogation qu'il me reste c'est que lorsque j'affiche "userData" dans la console, il ne m'indique pas l'entièreté du contenu du membre. Cependant, dans la GUI il le fait bel et bien... Etrange non ?
  • psychoh13psychoh13 Mothership Developer Membre
    20:03 modifié #19
    Mouais ok... évite comme je l'ai déjà  de te contenter d'une affectation pour userData, parce que si t'as déjà  appelé cette méthode avant, tu vas faire des fuites de mémoire... Enfin, pour le vérifier il faudrait que tu utilises Instruments avec le modèle de fuites mémoires.

    Et aussi, le ==YES est inutile voire même déconseillé... Avec un booléen il vaut mieux utiliser "present" pour YES et "!present" pour NO, c'est plus propre.
  • FreggFregg Membre
    20:03 modifié #20
    dans 1201011674:

    Mouais ok... évite comme je l'ai déjà  de te contenter d'une affectation pour userData, parce que si t'as déjà  appelé cette méthode avant, tu vas faire des fuites de mémoire... Enfin, pour le vérifier il faudrait que tu utilises Instruments avec le modèle de fuites mémoires.

    En fait, je crois que j'ai pris de l'avance (si je puis dire). Ma méthode n'est appelée qu'UNE seule fois dans UNE seul autre méthode au début de laquelle j'appelle une 3ème méthode (que j'ai appellée releaseAttribute) et qui remet tout les champs de mon objet a NULL (en cas de pointeur) et à  0 (en cas de BOOL/INT). Donc, quand j'appelle ma fonction "setUserData" je suis sur que le membre "userData" est vide (à  moins que je ne respecte pas les lignes de conduite que je me suis fixé)...

    N'est-ce pas suffisant ?

    dans 1201011674:

    Et aussi, le ==YES est inutile voire même déconseillé... Avec un booléen il vaut mieux utiliser "present" pour YES et "!present" pour NO, c'est plus propre.

    Ah, oki ! J'ai changer cela dans toutes mon code. Mais, puis-je demander pourquoi est-ce "déconseillé" ?
  • psychoh13psychoh13 Mothership Developer Membre
    20:03 modifié #21
    Bah disons que ce n'est pas très propre de faire comme ça d'ailleurs, d'ailleurs, il serait plus logique que ce soit ta méthode setUserData (qui devrait d'ailleurs changer de nom et s'appeler plutôt "refreshUserData", ou un truc du genre plutôt que "set..."), il serait plus logique que ce soit setUserData qui efface les données et qui les réécrive. Simplement parce que ce sont deux opérations liées, l'une doit toujours être faite avant l'autre et comme ça tu mets le risque d'erreur et d'étourderie à  zéro.

    Sinon, quand tu dis que tu mets les pointeurs à  NULL (pour les objets c'est "nil" même si c'est la même chose :D), j'espère que tu fais bien les release/autorelease/free appropriés.

    Pour ce qui des booléens, un booléen dans le cas le plus pur ne peut prendre comme valeur que YES et NO, mais en réalité, un booléen est, en C/Objective-C, une valeur nulle (0) représente un NO et tout autre valeur peut représenter un YES, donc tu peux avoir deux booléens à  vrai, faire == et que ça ne marche pas...
    Bien sûr, avec le type BOOL, on s'assure que ce ne sera pas le cas, il s'agit surtout d'une question de cohérence, quand tu utilises == ça veut dire que tu souhaites comparer une variable à  une valeur numérique, alors que si tu le fais en comparant un booléen et l'une de ses deux valeurs possibles, c'est redondant et c'est pas plus clair.
  • AliGatorAliGator Membre, Modérateur
    janvier 2008 modifié #22
    De plus écrire [tt]if (present == YES)[/tt] est "risqué" car si par erreur tu écris [tt]if (present = YES)[/tt] à  la place, tu fais une affectation au lieu d'une comparaison (à  cause du "=" à  la place du "==").
    Donc il suffit de taper un peu vite... une erreur est si vite arrivée.
    Alors que tu tu prends l'habitude de directement mettre [tt]if (present)[/tt] pas de risque de faute de frappe qui ferait tout foirer ;)

    Après c'est pas pour autant que c'est incorrect : c'est comme les conventions de nommage des variables, c'est plus pour prendre des bonnes habitudes dès le départ, mais ça fera pas buguer ton programme pour autant.
    (Sauf que si tu respectes les conventions, tu as des avantages supplémentaires car Apple a fait en sorte de prévoir des facilités avec des noms bien choisis -- grace à  ce que l'on appelle le "Key-Value Coding")
  • FreggFregg Membre
    20:03 modifié #23
    Oki, j'ai compris pour les booléen... Merci pour vos conseils...

    dans 1201016443:

    Bah disons que ce n'est pas très propre de faire comme ça d'ailleurs, d'ailleurs, il serait plus logique que ce soit ta méthode setUserData (qui devrait d'ailleurs changer de nom et s'appeler plutôt "refreshUserData", ou un truc du genre plutôt que "set..."), il serait plus logique que ce soit setUserData qui efface les données et qui les réécrive. Simplement parce que ce sont deux opérations liées, l'une doit toujours être faite avant l'autre et comme ça tu mets le risque d'erreur et d'étourderie à  zéro.

    Sinon, quand tu dis que tu mets les pointeurs à  NULL (pour les objets c'est "nil" même si c'est la même chose :D), j'espère que tu fais bien les release/autorelease/free appropriés.


    En fait... Mon programme a pour but de prendre un MP3 et d'en afficher les infos (header + tag). Oui, je sais, c'est trop compliquer pour commencer a programmer avec le concepte de OOP, Obj-c et Cocoa à  apprendre sur le tas... Mais comme je m'intéresse bcp a l'audio/video numérique j'avais déjà  pas mal de bases théoriques concernant le MP3. Du coup j'me suis dit que ce serait pas plus compliquer que d'ouvrir et de lire un fichier txt... Mal m'en a pris.

    Bref, tout ca pour dire que, lorsque l'utilisateur choisit un MP3 je remplis (d'ou le SETxxxxxx) différent champs de mon objet (ex : titre, artiste, .... etc.). Lorsque l'utilisateur choisit un second objet, tous les champs doivent être vidés puis re-remplis... C'est pour cela que, plutôt que de mettre dans chaque méthode setxxxxx (bien que je sois d'accord avec le fait que effacer le champ pour le remplir après sont deux opération liées) une ou deux lignes de code pour effacer le champs, j'ai mis toutes ces lignes dans ma méthodes "releaseAttributs", cela me semblait plus "propre", non ?
  • psychoh13psychoh13 Mothership Developer Membre
    20:03 modifié #24
    Non le problème n'est pas que tu as 2 méthodes séparées, au contraire, le problème c'est que leur exécution conjointe ne soit pas obligatoire.
    Ce que je te proposais, c'est d'appeler ton releaseAttributes à  l'intérieur de setUserData.
  • FreggFregg Membre
    20:03 modifié #25
    Tu veux dire qu'il veux mieux ne pas avoir de méthode "releaseAttribut" mais mettre dans chacune des méthodes "setxxxxxx" la remise a zéro du champ avant de le remplir...

    oki, ben j'vais changer cela alors...
  • schlumschlum Membre
    20:03 modifié #26
    dans 1201019785:

    Tu veux dire qu'il veux mieux ne pas avoir de méthode "releaseAttribut" mais mettre dans chacune des méthodes "setxxxxxx" la remise a zéro du champ avant de le remplir...

    oki, ben j'vais changer cela alors...


    Oui, c'est ça... Mais il faut aussi allouer au niveau du constructeur (peut être optionnel, mais au moins mettre à  "nil" dans ce cas) et désallouer au niveau du destructeur.
  • psychoh13psychoh13 Mothership Developer Membre
    20:03 modifié #27
    dans 1201020004:
    Oui, c'est ça... Mais il faut aussi allouer au niveau du constructeur (peut être optionnel, mais au moins mettre à  "nil" dans ce cas)


    C'est totalement optionnel, lors de l'allocation d'un objet Cocoa, toutes ses variables d'instance sont mises à  zéro, sauf bien sûr la variable d'instance isa. Donc, pas de problème de ce côté-là .
  • FreggFregg Membre
    20:03 modifié #28
    Je crois que j'ai un piste.. En fait, je pense que j'avais mal identifer le probleme...

    Je m'explique : cette NSMutableString, je l'affiche -dans ma GUI- dans un NSTextView (ou NSScrollView, il est marqué les deux). Et je me disais que, peut-être, pour ce type de champ, il faut faire un sorte de RESET une fois qu'on a afficher quelque chose dedans afin que le prochain affichage ne se fasse pas a la suite de ce qui a déjà  été écrit mais bien dans un beau champ tout vide...

    CEla pourrait expliquer mon problème... J'ai donc chercher une fonction "reset" (ou quelque chose s'y apparentant) mais je n'ai rien trouver jusqu'a présent.

    Ais-je raison : dois-je faire un "reset" sur ce type de champ ? Ou mon erreur vient bel et bien d'une de mes méthodes/variable...

    Merci beaucoup pour votre aide
  • psychoh13psychoh13 Mothership Developer Membre
    janvier 2008 modifié #29
    Normalement, si tu utilisais sur le NSTextField la méthode setStringValue: tu ne devais pas avoir ce problème. ???

    Je pense qu'au lieu d'affecter ta "tempString" à  "userData" directement, tu peux faire une copie de ta NSMutableString, ce qui la transformera en NSString et l'empêchera donc d'être modifiée :
    -(void) setUserData<br />{<br />&nbsp; &nbsp; NSMutableString * tempString = [[NSMutableString alloc] initWithCapacity:150];<br /><br />&nbsp; &nbsp; // code de modification de la tempString<br /><br />&nbsp; &nbsp; [userData release];<br />&nbsp; &nbsp; userData = [tempString copy];<br />&nbsp; &nbsp; [tempString release];<br />}<br />
    


    Alors, l'avant-avant-dernière ligne permet de supprimer l'ancien objet se trouvant dans userData.
    Ensuite, on crée une copie de la tempString, étant donné qu'il s'agit d'un type de classe qui différencie modifiable/non-modifiable, envoyer le message -copy sur une NSString ou une NSMutableString retournera une NSString, c'est-à -dire un objet non-modifiable.
    On l'affecte donc directement à  notre userData qui ne contiendra alors qu'une NSString non-modifiable au lieu d'une NSMutableString.
    Et pour finir, on supprime l'objet tempString étant donné qu'on vient de le copier et qu'il ne nous sert plus à  rien.
  • FreggFregg Membre
    20:03 modifié #30
    J'ai fait ce que tu as dit psychoh13, mais cela n'a rien changé ! J'ai alors fait plein d'affichage console dans tous les sens et ce que je peux dire c'est que lorsque je crée un nouvel objet sans "userData", mon champ est bien vide ! Seulement si le champ NSTextView de ma GUI a déjà  été rempli une fois, il reste rempli...

    Il semblerait donc que j'ai vu juste et que je doive trouver une manière de RESET mon NSTextView...
  • psychoh13psychoh13 Mothership Developer Membre
    janvier 2008 modifié #31
    Dans ce cas, j'aimerais bien que tu nous montres le message qui met à  jour le text field de ta GUI.

    [EDIT]
    Je retire ma question, je viens de retrouver le projet que tu m'as envoyé...

    Et donc je sais pourquoi ton truc marche, et comme par hasard c'est bien ce que je craignais...

    Regarde dans GUIControl.m, la méthode -openFile:, l'avant dernier message de cette méthode... :

    [_userDataTextField insertText:[soundFile getUserData]];
    


    La méthode "-insertText:" ajoute le texte à  la fin du texte déjà  écrit, donc forcément le reste du texte n'est pas supprimé !
    Mais fort heureusement, la super-classe de NSTextView (NSText) définit la méthode -setString: qui te permettra de faire la même chose que la méthode -setStringValue: des NSTextField...
    Fais ça et ton problème sera définitivement oublié. :D

    [EDIT 2]
    D'ailleurs, je ne comprends pas pourquoi tu as utilisé un NSTextView dans ce cas... Les NSTextView permettent d'écrire du texte sous forme de "rich text", c'est-à -dire qu'il est possible de mettre des couleurs sur certains mots et pas d'autres, de mettre des polices différentes selon les mots, etc.
    Ici, tu n'as pas besoin de cette fonctionnalité, tout le texte que tu écris dedans.
    Alors si tu as utiliser NSTextView parce que le volume tu pensais que les NSTextField se limitent à  une seule ligne... Bah désolémais tu te trompes, il suffit de modifier les propriétés de ton NSTextField pour lui permettre d'accepter plusieurs lignes et aussi d'ajouter des barres de défilement.

    Regarde par exemple la propriété "line breaks" qui permet d'indiquer au NSTextField, si du texte doit être coupé ou bien passer à  la ligne...
Connectez-vous ou Inscrivez-vous pour répondre.