Problème avec NSMutableString
Fregg
Membre
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.
et voici ce que la console m'affiche :
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...
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:@"TXXX"]; // recupère le nombre d'occurence de la chaine TXXX<br />for(j; j<i; ++j) { // pour j allant de 0 à i<br /> anObject = [self getFrameNamed:@"TXXX" atIndex:j]; // 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(@"\n");<br />NSLog(UserData); // 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...
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
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...
.
Cela me donne exactement la même chose...
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...
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 :
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.
Oki, donc, c'est une variable d'instance... Ok, je changerai les majuscule...
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.
Je ne comprends pas du coup pq elle n'est pas modifiable.
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 ?
???
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 :
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.
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.
En revanche, ce code générera un avertissement à la compilation mais marchera très bien lors de l'exécution :
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.
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:-
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...
En laissant "tempString" j'ai le bug suivant :
Il me répete donc deux fois "Rip date" plutot que de m'afficher les 5 valeur en une seule fois...
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 :
ce qui le fait afficher :
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
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
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 :
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.
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:-
Pour tes variables d'instance qui sont des pointeurs d'instance, il faut que tu définisses un setter :
Ensuite, tu utilises ce setter pour changer ta variable d'instance.
 Â
Sinon il vaut mieux écrire ça comme ça :
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...
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.
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 ?
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.
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 ?
Ah, oki ! J'ai changer cela dans toutes mon code. Mais, puis-je demander pourquoi est-ce "déconseillé" ?
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 ), 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.
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")
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 ?
Ce que je te proposais, c'est d'appeler ton releaseAttributes à l'intérieur de setUserData.
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.
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à .
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
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 :
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.
Il semblerait donc que j'ai vu juste et que je doive trouver une manière de RESET mon NSTextView...
[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... :
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é.
[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...