Sérialiser des données non-plist dans une plist

AliGatorAliGator Membre, Modérateur
23:58 modifié dans API AppKit #1
Bonjour les gens,

Voilà  je cherche à  sérialiser une classe qui a des variables d'instance scalaires, en particulier un NSPoint, pour sauver mes données dans un plist au final.
En fait j'ai un tableau de ces objets NamedPoints, chaque NamedPoint contenant une NSString et un NSPoint. Et je voudrais qu'ai final ces NSPoints soient convertis, par exemple en un NSDictionary avec les clés "x" et "y", au moment de la sérialisation, pour pouvoir obtenir un plist (puisqu'un NSPoint ne peut être encodé dans un plist de base)


1) J'ai déjà  réussi à  implémenter NSCoding pour sauver mon tableau de points sur disque (avec encodePoint:forKey: etc)... Mais le hic c'est que ça me produit un format propriétaire (une archive, quoi). Certes quand on regarde c'est un plist, mais avec des clés CF$UID et des trucs bizarres... pas ce que je veux quoi puisque moi je veux un plist tel qu'on le trouve quand on archive un NSArray classique (quand il ne contient que des plist-objets)

2) Dans la doc de "Archives and Serializations Programming Guide" j'ai aussi vu NSPropertyListSerialization, mais qui semble ne permettre de manipuler que les classes autorisées dans les PList. Ils expliquent bien comment substituer une NSColor par sa représentation NSData (dans l'exemple de la doc de NSUserDefaults vers laquelle ils renvoient) mais c'est à  appeler sur chaque NSColor à  archiver (dans mon cas ça serait sur chaque NSPoint... et encore NSPoint est une structure, pas une classe)

Il y a aussi quelques méthodes indiquant comment "prendre la main au moment de la sérialisation pour remplacer un objet par une représentation" (et c'est pile ce que je veux)... mais c'est indiqué que c'est deprecated et obsolète (classes NSSerializer et NSDeserializer) car remplacé par NSPropertyListSerialization....


Du coup je vois pas trop comment je pourrais dire à  ma classe NamedPoint "ah bah si tu veux t'encoder sous forme de plist, encode ce NSPoint sous forme de ce NSDictionary puisque tu ne sais pas stocker des NSPoint"...

Je pourrais créer un NSArray (parallèle à  mon tableau de NamedPoints) contenant la représentation que je veux pour ma sauvegarde (un NSArray de NSDictionary, chaque dico ayant une clé name, x et y), et sauver ce tableau... Mais l'idéal serait plutôt "d'expliquer" à  ma classe NamedPoint comment s'encoder pour lorsqu'elle doit être sérialisée dans un plist, sur le même principe que encodeWithCoder et initWithCoder mais pour les plist, quoi...


Des idées pour faire ça propre, du coup ?

Réponses

  • Philippe49Philippe49 Membre
    août 2009 modifié #2
    Profiter des représentations existantes/ajoutables en NSString ?
    Faire une catégorie pour le désarchivage initWithContentsOfFile: usingConventions:
  • CéroceCéroce Membre, Modérateur
    23:58 modifié #3
    Que donne l'enregistrement d'une NSValue créée par [NSValue valueWithPoint:] ?

    Mais bon, ça ne résout pas grand chose, dans tous les cas, il faut qu'une classe transforme le point.
  • AliGatorAliGator Membre, Modérateur
    23:58 modifié #4
    Alors oui pour les deux réponses, dans tous les cas que j'utilise [NSValue valueWithPoint:pt] ou NSStringFromPoint(pt)... je les appelle où ?

    Soit je reconstruit comme je disais un NSArray contenant un NSDictionary par NamedPoint à  sauver, et je crée donc ça à  la main avec une boucle for qui transforme chaque NamedPoint en NSDictionary... le temps de sauver ce NSArray temporaire... Mais ça je voudrais éviter car ce n'est pas évolutif (si c'est un NSDictionary de NSArray de ... de NamedPoints que je dois sauver un jour ? Je reproduit toute l'arborescence à  chaque fois ? berk)

    Soit je trouve un moyen de dire à  ma classe NamedPoint "bon si on te demande de te sérialiser/sauver sur disque/..., n'essaye pas de te sauver toi-même (car tu réaliserais que tu contiens un NSPoint qui n'est pas un "Plist-Object" donc tu refuserais), mais essaye de sauver ta "représentation plist-ique".

    En gros avoir une sorte de méthode -(id)substituteObjectForSerialisation et -(void)initWithSubstitueObject: ... ou une sorte de ValueTransformer ou je ne sais quoi, qui permettrait à  mon NamedPoint de fournir une représentation de lui-même juste pour la sauvegarde dans un plist.

    Un peu comme encodeWithCoder permet à  un objet d'indiquer comment il doit être encodé pour la sérialisation... Ou comme classForCoder, replacementObjectForCoder etc... de NSCoding (mais moi je veux un plist, pas une archive)
  • Philippe49Philippe49 Membre
    23:58 modifié #5
    dans 1250071800:

    Soit je trouve un moyen de dire à  ma classe NamedPoint "bon si on te demande de te sérialiser/sauver sur disque/..., n'essaye pas de te sauver toi-même (car tu réaliserais que tu contiens un NSPoint qui n'est pas un "Plist-Object" donc tu refuserais), mais essaye de sauver ta "représentation plist-ique".

    En gros avoir une sorte de méthode -(id)substituteObjectForSerialisation et -(void)initWithSubstitueObject: ... ou une sorte de ValueTransformer ou je ne sais quoi, qui permettrait à  mon NamedPoint de fournir une représentation de lui-même juste pour la sauvegarde dans un plist.


    Cela c'est facile à  faire, une NSString (description ou autres), un NSData fait l'affaire (encode). Le problème est dans le désarchivage, car on n'a pas le moyen de mettre un tag dans le plist indiquant la règle du désarchivage.
    A moins ... de mettre un métada dans le NSData (un entête dans la NSString) et de faire une custom-méthode (sans doute récursive) de lecture du plist. C'est l'idée de initWithContentsOfFile: usingConventions:
  • Philippe49Philippe49 Membre
    août 2009 modifié #6
    Ou carrément de rajouter une option custom autre que NSString, NSData ... dans des pALIst , et d'utiliser NSXML pour les parser.
  • yoannyoann Membre
    23:58 modifié #7
    Sauf si tu fait ton propre parseur xml je ne vois pas comment tu peut faire vu que l'intérêt du plist est d'avoir des clef standardisé et un nombre de type de donnée fini.

    Ce que je ferais à  ta place, si ta classe NamedPoints ne contiens que une NSString, et un NSPoint c'est remplacer ça par un dictionnaire avec 3 clef name, x, y

    Ensuite tu peut te créer des macro ou fonction du style npIntForX(dict) qui te renvois le résultat de [[dict valueForKey:@x] intValue]

    Autre possibilité à  tester, sous classer NSDictionary pour avoir une API compatible avec ta classe actuelle et faire des getter et des setters qui gère la correspondance.
  • MalaMala Membre, Modérateur
    23:58 modifié #8
    dans 1250076852:

    Autre possibilité à  tester, sous classer NSDictionary pour avoir une API compatible avec ta classe actuelle et faire des getter et des setters qui gère la correspondance.

    NSDictionary est un classe "Cluster". On ne peut pas sous classer.


    NSDictionary and NSMutableDictionary are part of a class cluster, so the objects you create with this interface are not actual instances of the these two classes. Rather, the instances belong to one of their private subclasses. Although a dictionary's class is private, its interface is public, as declared by these abstract superclasses, NSDictionary and NSMutableDictionary.

  • yoannyoann Membre
    23:58 modifié #9
    dans 1250079095:

    dans 1250076852:

    Autre possibilité à  tester, sous classer NSDictionary pour avoir une API compatible avec ta classe actuelle et faire des getter et des setters qui gère la correspondance.

    NSDictionary est un classe "Cluster". On ne peut pas sous classer.


    NSDictionary and NSMutableDictionary are part of a class cluster, so the objects you create with this interface are not actual instances of the these two classes. Rather, the instances belong to one of their private subclasses. Although a dictionary's class is private, its interface is public, as declared by these abstract superclasses, NSDictionary and NSMutableDictionary.



    C'est pour ça que j'ai mis a tester, même si j'aurais du mettre a vérifier, je n'étais plus certain du fait que NSDictionary soit cluster ou pas
  • AliGatorAliGator Membre, Modérateur
    août 2009 modifié #10
    Oui j'ai pas l'impression qu'on puisse finalement. Ou alors en effet mettre un NSDictionary comme ivar (mais alors si j'ai un NSArray de NamedPoints même si eux-même ne contiennent qu'un NSDictionary, suis pas sûr que ça suffise à  writeToFile:atomically: pour sauver mon NSArray dans un plist, je suis même quasi sûr que non... d'où l'idée de la sous-classe de NSDictionary, mais pb de class cluster... arg)


    Philippe, je pense que tu n'as pas compris exactement ce que je demandais. Faire une méthode, moi-même, qui convertit mon NamedPoint en une classe plist-isable (un NSDictionary par exemple), ça bien sûr que ce n'est pas méchant. Avec la méthode description ou autre d'ailleurs qui peut aussi faire l'affaire.

    Le truc c'est que si j'appelle "writeToFile:atomically:" sur mon NSArray contenant mes NamedPoints, il va refuser (me retourner NO) puisque les objets contenus dans le NSArray ne sont pas plist-ables, en particulier les NSPoints de mes NamedPoints...
    La difficulté c'est pas de créer une méthode qui me retourne un NSDictionary pour représenter mon NamedPoint pour la sauvegarde... c'est de trouver s'il n'y a pas une méthode existante pour ça automatiquement appelée sur tes objets quand on fait writeToFile:atomically: sur un NSArray.
    Pour que quand je fasse [namedPointsArray writeToFile:toto atomically:YES] il m'écrive tout seul le plist en demandant à  chacun de mes NamedPoint une représentation plist-ique d'eux-même (via cette fameuse hypothétique méthode prévue pour) pour pouvoir les écrire dans le plist justement.
    Et ça c'est pas gagné apparemment...
  • Philippe49Philippe49 Membre
    23:58 modifié #11
    dans 1250085077:

    Philippe, je pense que tu n'as pas compris exactement ce que je demandais.
    Le truc c'est que si j'appelle "writeToFile:atomically:" sur mon NSArray contenant mes NamedPoints, il va refuser (me retourner NO) puisque les objets contenus dans le NSArray ne sont pas plist-ables, en particulier les NSPoints de mes NamedPoints...

    Si, c'est bien ce que j'avais compris, mais pour moi cela passe par des méthodes de catégorie sur les collections NSArray et NSDictionary à  implémenter parce que le fonctionnement des méthodes clés writeToFile et initWithContentsOfFile est opaque. Il faudrait une méthode qui renvoie une array/dictionary où les objets sont transformés si ils appartiennent à  un certain groupe de classes, la récursion étant un peu pénible je l'accorde.
    Intéressant, je vais finir par le laisser piéger à  ton truc !  ::)

       

    Une autre piste, à  voir jusqu'où on peut la pousser : surclasser NSArray/NSDictionary , et définir un protocole formel ou informel.

    @interface PLArray : NSObject &lt;PLArrayProtocol&gt; {<br />&nbsp;  NSArray * array;&nbsp; &nbsp; // l&#39;array classique<br />}<br />@property (assign) NSArray * array;<br />-(void) writeToFile: ...<br />+(NSArray *) initWithContentsOfFile: <br />@end
    


    et dans le code des tests pour voir si les méthodes -(id)serializedObject et -(void)initWithSerializedObject: sont implémentées par les objets de l'array.
  • Philippe49Philippe49 Membre
    23:58 modifié #12
    dans 1250089674:

    Intéressant, je vais finir par me laisser piéger à  ton truc !  ::)

    Ca y est, j'ai craqué !
       

    Un petit essai :
    <br />#import &lt;Foundation/Foundation.h&gt;<br /><br />@protocol serializable<br />-(id) serializedObject;<br />@end<br /><br />@interface MyPoint:NSObject &lt;serializable&gt; {<br />	NSPoint p;<br />}<br />-(id) initWithX:(float)x y:(float)y;<br />@end<br /><br />@implementation MyPoint<br />-(id) initWithX:(float)x y:(float)y {<br />	self=[super init];<br />	if(self) {<br />		p=NSMakePoint(x,y);<br />	}<br />	return self;<br />}<br />-(id) serializedObject {<br />	return [@&quot;&lt;MyPoint&gt;&quot; stringByAppendingString:NSStringFromPoint(p)];<br />}<br />@end<br /><br /><br />@interface NSArray (serialization) <br />-(void)writeToFile:(NSString*) path atomically:(BOOL)flag usingSerializableProtocol:(BOOL)flag2 ;<br />-(NSArray *) serializableArray ;<br />@end<br />@implementation NSArray (serialization) <br />-(void)writeToFile:(NSString*) path atomically:(BOOL)flag usingSerializableProtocol:(BOOL)flag2 {<br />	if(!flag2) {<br />		[self writeToFile:path atomically:flag];<br />	}<br />	[[self serializableArray] writeToFile:path atomically:flag];<br />}<br /><br />-(NSArray *) serializableArray 	{<br />	NSMutableArray * array=[NSMutableArray array];<br />	for(id object in self) {<br />		if([object conformsToProtocol:@protocol(serializable)]){<br />			[array addObject:[object serializedObject]];<br />		} else if ([object respondsToSelector:@selector(serializableArray)]){<br />			[array addObject:[object serializableArray]];<br />		} else {<br />			[array addObject:object];<br />		}<br />	}<br />	return [array copy];<br />}<br /><br />@end<br /><br /><br /><br />int main (int argc, const char * argv&#91;]) {<br />	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];<br />	MyPoint * a=[[MyPoint alloc] initWithX:0 y:0];<br />	MyPoint * b=[[MyPoint alloc] initWithX:10 y:10];<br />	MyPoint * c=[[MyPoint alloc] initWithX:20 y:20];<br />	NSArray * subarray=[NSArray arrayWithObjects:a,b,c,nil];<br />	NSArray * array=[NSArray arrayWithObjects:<br />									 a,<br />									 subarray,<br />									 [NSArray arrayWithObjects:subarray,a,nil],<br />									 nil<br />									 ];<br />	NSLog(@&quot;%@&quot;,array);<br />	NSLog(@&quot;%@&quot;,[array serializableArray]);<br />	<br />	NSString * path=[NSHomeDirectory() stringByAppendingPathComponent:@&quot;Desktop/toto.plist&quot;];<br />	[array writeToFile:path atomically:YES usingSerializableProtocol:YES];<br />	[pool drain];<br />	return 0;<br />}<br /><br />
    



    et le plist créé
  • Philippe49Philippe49 Membre
    23:58 modifié #13
    Peut-être améliorer en rendant le protocole informel, et en définissant une catégorie sur NSValue ?
  • AliGatorAliGator Membre, Modérateur
    23:58 modifié #14
    Et quitte à  faire un protocole informel, autant utiliser les mêmes méthodes pour retourner la version sérialisée de l'objet (serializedObject) que ce soit un objet à  toi ou ton NSArray dans sa version sérialisée (serializedArray), en plus ;) Plus générique ;)

    Merci, je tâche de voir ça. Du coup faut faire une catégorie pour tous les types acceptés dans une plist (NSArray et NSDictionary surtout en tout cas), je vais creuser aussi :)
  • mpergandmpergand Membre
    août 2009 modifié #15
    Difficile de rendre propre un truc crade !

    Je veux parler de l'utilisation de structures C dans un langage objet.
    Franchement les NSRect et consorts  pouah !

    J'ai utilisé les bidouilles décrites plus haut, pour gérer les prefs des applis et les prefs des documents (packages).
    J'ai deux classes AppPrefs et SheetPrefs qui se gèrent comme un NSUserDefauts/NSDictionary, mais comme ce ne sont ni un NSUserDefauts ni un dictionnaire, j'utilise du forwardInvocation ...

    Ou comment faire un truc crade 100% cocoa compliant  :)
Connectez-vous ou Inscrivez-vous pour répondre.