Stocker une chaà®ne obtenue par référence dans un NSMutableDictionary

laudemalaudema Membre
juillet 2010 modifié dans API AppKit #1
Bonjour,
Une méthode a pour objet de parcourir un fichier texte d'une série de lignes au format "clef=valeur" et d'en extraire un dictionnaire
<br />NSString *key, *value;<br />while (! [subScan isAtEnd]) {				<br />	[subScan scanUpToString:@&quot;=&quot; intoString:&amp;key];<br />	[subScan scanString:@&quot;=&quot; intoString:NULL];<br />	[subScan scanUpToString:@&quot;&#092;r&#092;n&quot; intoString:&amp;value];<br />	[subDic setObject:value forKey:key];				<br />}


Mais ça marche pô, mon mutable dictionary reste vide.
Par contre si je remplace setObject par setValue
[subDic setValue:value forKey:key];
Là  ça fonctionne mais j'ai des doutes sur la pérénnité de value en sachant que le dictionnaire la contenant sera mis dans un tableau avant d'être relâché puis le tableau renvoyé "autoreleased" à  une autre classe.
Serait il plus sage de mettre setObject:[NSString stringWithString:value] ?

Réponses

  • GreensourceGreensource Membre
    18:09 modifié #2
    C'est un peu étrange ton truc, normalement setObject:forKey: fait un retain, tu aurais un souci si tu essayais de mettre nil mais tu aurais une exception donc je suppose que ce n'est pas ça.

    Il se passe quoi dans tes méthodes:
    scanUpToString:intoString: et scanString:intoString: ?
  • laudemalaudema Membre
    18:09 modifié #3
    Si setObjectForKey fait un retain c'est déjà  un souci de moins mais que retain t-il reste une bonne question.
    La méthode utilise un scanner de la classe NSScanner. à  un objet scanner on passe des variables par référence: on déclare une NSString, un int ou un float etc et on passe l'adresse de la variable à  la méthode http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Strings/Articles/Scanners.html#//apple_ref/doc/uid/20000147-BCIEFGHC , c'est ce que j'appelais "chaà®ne obtenue par référence"
    Si je fais un log de mes chaines elles sont bien là  dans le terminal. À chaque itération l'adresse change et le type est un NSCFString.
    Donc pas forcément un objet au sens Objective-C et pourtant une chaà®ne.
  • laudemalaudema Membre
    18:09 modifié #4
    Donc comme ça ça marche
    <br />[subDic setObject:(NSString*)value forKey:key];<br />
    

    J'aurais dû y penser avant puisque
    NSString is “toll-free bridged” with its Core Foundation counterpart, CFString (see CFStringRef).
    http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/NSString.html#//apple_ref/doc/uid/20000154-10850
    Alors on peut faire un cast ...
  • ChachaChacha Membre
    juillet 2010 modifié #5
    dans 1279204714:

    Donc comme ça ça marche
    <br />[subDic setObject:(NSString*)value forKey:key];<br />
    



    Ici, caster la chaà®ne ne change absolument rien pour le code généré. L'objet ne change pas, les retain/release seront rigoureusement les mêmes. Il n'est pas normal à  la base que setObject:forKey: et setValue:forKey: se comportent différemment dans ton morceau de code, il doit y avoir un problème ailleurs.
    Si la mémoire n'est pas critique, au lien des NSScanner, et que tu es sûr que tes chaà®nes ne contiennent pas le caractère "=", tu peux te simplifier la vie avec "componentsSeparatedByCharactersInSet:" et  "componentsSeparatedByString:". Si tu veux faire ça rigoureux, ben... regex ! (c'est à  la mode on dirait  :D)
    Mais faudrait logger un peu ce qui se passe, parce qu'à  moins que la chaà®ne contenue dans "key" commence par un "@", setValue:forKey: et setObject:forKey: devraient faire la même chose ici.
    T'es sûr qu'une exception est pas levée dans certains cas pour un key ou une value qui se retrouverait égal à  nil ?
  • laudemalaudema Membre
    18:09 modifié #6
    Oui, j'aurais pu le faire avec "componentsSeparatedByString", d'ailleurs c'est ce que faisais avant, j'ai pris un NSScanner pour voir si je le maitrisais mieux depuis un an que je m'y étais frotté mais surtout parce que setCharactersToBeSkipped me permet d'eviter des espaces ou des retours chariots dans les chaà®nes retournées, plutôt que de les chercher et supprimer moi même.
    J'ai regardé les regex mais ça n'existe pas dans Cocoa il faut utiliser une librairie externe ou passer par un script.
    Par contre il me semble (relativement) logique que setObject:ForKey: ne fonctionne pas car ce qui est retourné à  travers &value n'est pas un objet mais un "typedef const struct __CFString * CFStringRef" donc l'appel est ignoré car ce n'est pas nil non plus !

    Aucune exception, aucune modification, je pourrais commenter la ligne j'aurais le même résultat !

    setValue:ForKey utilise setObject:ForKey après avoir vérifié que value!=nil, c'est peut être là  que value qui est une structure  devient un objet Cocoa.
    Apple donne t'il le source de ces méthodes ? J'aurais aimé y jeter un oeil ;)

  • ChachaChacha Membre
    18:09 modifié #7
    dans 1279256193:

    J'ai regardé les regex mais ça n'existe pas dans Cocoa il faut utiliser une librairie externe ou passer par un script.

    Ouaip. NSPredicate est un peu limité, mais regexkitlite est un super framework (à  regarder à  l'occasion, là  on déborde du sujet qui nous préoccupe).

    dans 1279256193:

    Par contre il me semble (relativement) logique que setObject:ForKey: ne fonctionne pas car ce qui est retourné à  travers &value n'est pas un objet mais un "typedef const struct __CFString * CFStringRef" donc l'appel est ignoré car ce n'est pas nil non plus !

    Caster ne fait que réinterpréter le pointeur pour que le compilateur ne râle pas, mais ici ça ne va rien changer du tout. De toutes façons, c'est encore casté en (id) pour être passé en paramètre à  setObject/Value:forKey:


    setValue:ForKey utilise setObject:ForKey après avoir vérifié que value!=nil, c'est peut être là  que value qui est une structure  devient un objet Cocoa.


    Une NSString* est une CFStringRef, et inversement ; ce sont des pointeurs, il n'y a pas de conversion interne ; comme tu l'as dis, ça fait partie des classes "toll free bridge" entre Cocoa et CoreFoundation.

    Un truc tout bête : est-ce que si tu isoles complètement ton code (du genre qu'on puisse le reproduire sur nos machines), ça donne le même problème ?
  • mpergandmpergand Membre
    juillet 2010 modifié #8
    componentsSeparatedByString c'est de la balle  :D

    J'ai fait un essai avec un fichier .strings:
    // french

    "error" = "erreur";
    Error = Erreur;
    "copy file" = "copie fichier";
    "delete" = "supprime";

    -(void) awakeFromNib<br />{<br />	NSString* stringPath=[[NSBundle mainBundle] pathForResource:@&quot;Localizable&quot; ofType:@&quot;strings&quot;];<br />	NSString* strings=[NSString stringWithContentsOfFile:stringPath encoding:NSUTF8StringEncoding error:nil];<br />	<br />	if(strings==nil)<br />		{<br />		printf(&quot;erreur chargement&#092;n&quot;);<br />		return;<br />		}<br />	<br />	NSMutableDictionary* dic=[NSMutableDictionary dictionary];<br />	NSArray* lines=[strings componentsSeparatedByString:@&quot;&#092;n&quot;];<br />	NSLog(@&quot;%@&quot;,lines);<br />	<br />	int l;<br />	<br />	for(l=0;l&lt;[lines count];l++)<br />		{<br />		NSString* line=[lines objectAtIndex:l];<br />		NSArray* components=[line componentsSeparatedByString:@&quot;=&quot;];<br />		<br />		if([components count]!=2) continue;<br />		<br />		NSString* key=[components objectAtIndex:0];<br />		NSString* value=[components objectAtIndex:1];<br />		<br />		key=[key stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@&quot; &#092;&quot;&quot;]];<br />		value=[value stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@&quot; ;&#092;&quot;&quot;]];<br />		[dic setObject:value forKey:key];<br />		}<br />		<br />	NSLog(@&quot;%@&quot;, dic);<br />}
    


    et ça donne pour le dico:
    2010-07-16 11:00:57.624 StringsToDic[690:10b] {
        Error = Erreur;
        "copy file" = "copie fichier";
        delete = supprime;
        error = erreur;
    }

  • laudemalaudema Membre
    18:09 modifié #9
    dans 1279259949:

    Caster ne fait que réinterpréter le pointeur pour que le compilateur ne râle pas, mais ici ça ne va rien changer du tout. De toutes façons, c'est encore casté en (id) pour être passé en paramètre à  setObject/Value:forKey:

    Si c'est encore casté id alors je ne devrais pas avoir besoin de caster NSString ?
    Tu as vu ça où ?

    dans 1279259949:

    Un truc tout bête : est-ce que si tu isoles complètement ton code (du genre qu'on puisse le reproduire sur nos machines), ça donne le même problème ?
    `
    C'est déjà  du code isolé, je teste ça dans une application "Foundation Tool" et si ça marche je le ré-incorpore dans mon appli.
    Voici le code copié tel quel, une partie sert à  enlever les commentaires( lignes commençant par un ;), là  le but était de me familiariser avec NSRange dans une chaà®ne. C'est sûr que, pour ça, travailler sur un tableau c'est plus facile 8--)

    <br />#import &lt;Foundation/Foundation.h&gt;<br /><br />int main (int argc, const char * argv&#91;]) {<br />&nbsp; &nbsp; NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];<br />//Je sais, c&#39;est pas beau mais pas trouvé comment faire autrement...<br />	NSString *zePath = [[NSBundle mainBundle] resourcePath];<br />	if ([[zePath lastPathComponent] isEqualToString:@&quot;Debug&quot;]) {<br />		zePath=[zePath stringByDeletingLastPathComponent];<br />		zePath=[zePath stringByDeletingLastPathComponent];	<br />		zePath=[zePath stringByAppendingFormat:@&quot;/FichierTest.txt&quot;];<br />		zePath = [zePath stringByStandardizingPath];<br />	}	 <br />	NSError *e;<br />	NSString *depart = [NSString stringWithContentsOfFile:zePath<br />												 encoding:NSISOLatin1StringEncoding<br />													error:&amp;e];<br />	<br />	if (! depart)<br />		NSLog(@&quot;erreur n° %i : %@&quot;,[e code], [e localizedDescription]), return 1;<br />	<br />	NSMutableArray *resultAr = [[NSMutableArray alloc]init];<br />	NSArray *blocs = [depart componentsSeparatedByString:@&quot;&#092;r&#092;n&#092;r&#092;n&quot;];<br />	//	printf(&quot;%i blocs&#092;r&quot;,(int)[blocs count]);<br />	NSCharacterSet *pointVirgule =[NSCharacterSet characterSetWithCharactersInString:@&quot;;&quot;];<br />	NSCharacterSet *newLine = [NSCharacterSet newlineCharacterSet];<br />	NSCharacterSet *newLineAndWhiteSpace = [NSCharacterSet whitespaceAndNewlineCharacterSet];<br />	NSString *subName, *key, *value;<br />	for ( NSString *sub in blocs){<br />		NSMutableDictionary *subDic = [[NSMutableDictionary alloc]init];<br />		//Scanner ne fonctionnera pas si commentaire...<br />		while ([sub rangeOfCharacterFromSet:pointVirgule].location !=NSNotFound) {<br />			NSRange rangeOfPointVirgule = [sub rangeOfCharacterFromSet:pointVirgule];<br />			NSRange rangeOfNewLine = [[sub substringFromIndex:rangeOfPointVirgule.location]<br />									&nbsp; rangeOfCharacterFromSet:newLine];<br />			NSRange supprimons = NSMakeRange(rangeOfPointVirgule.location, rangeOfNewLine.location);<br />			sub =[sub stringByReplacingCharactersInRange:supprimons withString:@&quot;&quot;];			<br />		}<br />		NSScanner *subScan = [NSScanner scannerWithString:sub];<br />		[subScan setCharactersToBeSkipped:newLineAndWhiteSpace];<br />		[subScan scanUpToString:@&quot;[&quot; intoString:NULL];<br />		[subScan scanString:@&quot;[&quot; intoString:NULL];<br />		[subScan scanUpToString:@&quot;]&quot; intoString:&amp;subName];<br />		[subScan scanString:@&quot;]&quot; intoString:NULL];<br />		[subDic setObject:subName forKey:@&quot;Bloc&quot;];		<br />		while (! [subScan isAtEnd]) {				<br />			[subScan scanUpToString:@&quot;=&quot; intoString:&amp;key];<br />			[subScan scanString:@&quot;=&quot; intoString:NULL];<br />			[subScan scanUpToString:@&quot;&#092;r&#092;n&quot; intoString:&amp;value];<br />			[subDic setObject:(NSString*)value forKey:key];<br />		}<br />		[resultAr addObject:subDic];<br />		<br />		[subDic release];<br />		<br />	}<br />	//NSPredicate *allKeys = [NSPredicate predicateWithFormat:@&quot;Bloc like &#39;*&#39;&quot;];<br />	NSArray *result2&nbsp; = [resultAr valueForKey:@&quot;Bloc&quot;];<br />	printf(&quot;%i objets dans resultArr %i dans resultArray.filteredArrayUsingPredicate&#092;n&quot;, (int)[resultAr count],(int)[result2 count]);<br />	for (NSDictionary *dico in resultAr){<br />		printf(&quot;Le Bloc **%s**&#092;n&quot;,[(NSString*)[dico objectForKey:@&quot;Bloc&quot;]UTF8String]);<br />		<br />		[dico enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {<br />			if (![(NSString*)key isEqualToString:@&quot;Bloc&quot;]){<br />				printf(&quot;%s: %s&#092;n&quot;,[(NSString*)key UTF8String],[(NSString*)obj UTF8String]);<br />			}<br />			<br />			<br />		}];<br />		printf(&quot;&#092;n&quot;);<br />	}<br />	<br />	//&nbsp; &nbsp; NSLog(@&quot;%@&quot;,resultAr);<br />	[resultAr release];<br />&nbsp; &nbsp; // insert code here...<br />&nbsp; &nbsp; //NSLog(@&quot;Hello, World!%@&quot;,zePath);<br />&nbsp; &nbsp; [pool drain];<br />&nbsp; &nbsp; return 0;<br />}<br />
    

    Et éventuellement un lien pour "FichierTest.txt" http://voui.free.fr/Telechargements/FichierTest.txt
    Je suis très mécontent de ne pas savoir l'ouvrir avec la ligne de code de mpergand d'ailleurs, c'est ce que j'avais tenté en premier mais sans succès
    dans 1279271131:

    <br />NSString* stringPath=[[NSBundle mainBundle] pathForResource:@&quot;Localizable&quot; ofType:@&quot;strings&quot;];<br />
    


    Chez moi
    <br />NSString* stringPath=[[NSBundle mainBundle] pathForResource:@&quot;FichierTest&quot; ofType:@&quot;txt&quot;];<br />
    

    ça ne fonctionne pas non plus d'où cet horrible bricolage au début :(
  • tabliertablier Membre
    18:09 modifié #10
    Faire un mutabledictionary avec un fichier de type .strings est très simple:

        NSMutableDictionary *leDico = [NSMutableDictionary dictionaryWithCapacity:10]  ;
        [leDico  setDictionary:[NSDictionary initWithContentsOfFile:cheminDuFichier]] ;

    Bien sur, il faut être sur de la syntaxe du fichier traité.
  • mpergandmpergand Membre
    18:09 modifié #11
    dans 1279286700:

    Chez moi
    <br />NSString* stringPath=[[NSBundle mainBundle] pathForResource:@&quot;FichierTest&quot; ofType:@&quot;txt&quot;];<br />
    

    ça ne fonctionne pas non plus d'où cet horrible bricolage au début :(


    Normal, un fondationTool n'est pas un bundle ...

    Bon, mon code marche avec ton fichier, mais le problème c'est qu'il y a plusieurs entrées avec le même nom (date ...)
    Il faut donc faire un parse par section.
  • laudemalaudema Membre
    18:09 modifié #12
    Entre les deux j'ai fait un Build -> Clean all targets, depuis subDic retient toutes ses clefs et valeurs.
    Pourtant ce matin je testais encore en enlevant le cast sur key il retenait, en enlevant sur value il retenait plus. Maintenant il retient tout..
    dans 1279289304:

    Normal, un fondationTool n'est pas un bundle ...

    Bah oui, je l'avais oublié, d'où son entêtement à  se voir dans le répertoire /build/Debugg/ il se situe au niveau de l'exécutable... logique as usual :/
    Mais bon, sans tâtonner je n'aurais pas cherché à  "clean all targets" et je ne saurais pas que finalement mon code est valide et que je vous ai dérangé pour rien.

    Mille excuses

    Finalement je pense garder quand même les explode, c'est vrai que ça va vite et puis c'est plus facile pour traiter les commentaires et autres lignes sans "=". Et la prochaine fois je n'hésiterais pas non plus à  me servir de NSScanner si besoin ...

    Merci à  tous
  • ChachaChacha Membre
    18:09 modifié #13
    dans 1279286700:

    Si c'est encore casté id alors je ne devrais pas avoir besoin de caster NSString ?
    Tu as vu ça où ?

    setObject/Value:(id)value forKey:(id)key prennent des id. Donc les paramètres sont castés en (id) à  l'appel de la fonction. Mais ça ne change rien, c'est juste une interprétation de l'adresse contenue dans le pointeur pour faire plaisir au compilateur (et lever des warnings si c'est louche).
    Donc, non, caster en NSString* ne sert à  rien ici.
  • laudemalaudema Membre
    18:09 modifié #14
    dans 1279346261:

    setObject/Value:(id)value forKey:(id)key prennent des id. Donc les paramètres sont castés en (id) à  l'appel de la fonction.

    Maintenant que tu le dis ça me saute aux yeux 
    Jusque là  je pensais qu'il n'y avait qu'une vérification de faite, je n'imaginais pas que c'était de cette manière.
  • AliGatorAliGator Membre, Modérateur
    18:09 modifié #15
    Heu dites, comme la syntaxe de ton fichier (suite de lignes [tt]clé="valeur"[/tt]) ressemble très fortement à  un fichier .strings (ou à  un fichier .plist écrit au format texte ASCII, qui n'est plus trop le plus conseillé mais reste encore tout à  fait supporté), pourquoi ne pas directement le lire avec Cocoa ? En le lisant comme un PLIST, avec un dictionaryWithContentsOfFile, quoi (un peu comme le propose tablier d'ailleurs).

    Ca se fait en une seule ligne et basta.
  • laudemalaudema Membre
    juillet 2010 modifié #16
    Bonjour Ali, merci de te pencher sur mon sort.

    dictionaryWithContentOfFile
    Return Value
    ....  nil .. if the contents of the file are an invalid representation of a dictionary

    Or ce n'est pas moi qui crée le fichier mais une appli tierce, venant du monde PC, et qui ne semble pas savoir ce qu'est une "valid representation of a dictionary", en tout cas pour Cocoa.
    Corriger le fichier pour qu'il soit lisible par dictionaryWithContentOfFile serait encore plus compliqué

    PS: Où trouver la documentation sur la petite icône à  droite de chaque titre des messages du forum (plaque de chocolat) ? La tasse pleine ou vide est "self-documented" mais je me demande ce que représente la petite plaque de chocolat  8--)
  • GreensourceGreensource Membre
    18:09 modifié #17
    Juste pour ton postscriptum, la coloration de la tablette indique la difficulté estimé du sujet, ça va du chocolat blanc pour les débutants au noir pour les discussion "experte".
  • laudemalaudema Membre
    18:09 modifié #18
    Viele Danke  :)
Connectez-vous ou Inscrivez-vous pour répondre.