Meilleure stratégie (ou macros pour simplifier?) pour une structure en arbre

AliGatorAliGator Membre, Modérateur
juillet 2009 modifié dans API AppKit #1
Bonjour à  tous,

Voilà  j'ai besoin dans mon modèle d'avoir une structure, hiérarchisée pour plus de clareté, représentant un objet.
Par exemple on va dire qu'il s'agit d'un logiciel, qui a comme attributs un auteur (nom, prénom, date de naissance), une description (nom, version, date), une société (nom, statut, adresse), une plateforme. (En réalité ma structure est bien plus complexe et a bien plus d'attributs)
L'idée est de structurer mes données, plutôt que d'avoir un gros NSObject qui contienne toutes ces données à  plat.

Mais ma question, c'est comment générer les classes pour cette structure de données sans avoir 1500 lignes de glue code rébarbatif ?

Besoins souhaités :
- Structure facilement parcourrable par code (notation pointée des @property souhaitée)
- Structure sérialisable/archivable (dans un plist ou du genre)
- KVC-compliant, en tout cas supportant les KeyPaths

Solutions possibles :
  • un bête NSDictionary : c'est KVC, facilement sérialisable... mais on peut enregistrer un peu n'importe quoi dedans donc côté error-checking des clés c'est pas terrible. Et côté facilement parcourable c'est pas ça : on peut bien faire un [tt][dict valueForKeyPath:@machin.truc.bidule][/tt] mais je préférerais qd mm une notation directe dict.machin.truc.bidule
  • Une classe perso dérivant de NSObject avec les variables d'instance qui vont bien


C'est cette dernière vers laquelle je me suis dirigée, en déclarant mes vi en @property. Donc en gros j'ai les classes Auteur, Description, Societe, et une classe Logiciel qui contient une vi "NSString* plateforme" et une vi Auteur*, une Description* et une Societe*.

Mais le truc c'est que du coup faut implémenter plein de trucs rébarbatifs (init, dealloc, initWithCoder, encodeWithCoder, ...)

Du coup je me demandais si vous aviez des idées d'alternatives plus simples à  mettre en oeuvre (non pas que le code soit compliqué, mais plutôt lassant en fait).
Ou alors des idées d'astuces, genre macros preprocesseur à  la #define, pour éviter de répéter tout le temps du code qui se ressemble...
- Dans la déclaration des vi
- Dans la déclaration des @property associées
- Dans le init quand j'alloc les classes "conteneur" (mais pas les feuilles de ma structure, pas besoin ça)
- Dans le dealloc quand je release mes vi
- Dans le encodeWithCoder
- Dans le initWithCoder
---> Dans chacune de ces méthodes on met un peu toujours les mêmes choses, et on boucle sur les variables d'instance, c'est rébarbatif.

Mais je ne vois pas comment, avec une macro ou autre (pour aussi que si je modifie ma structure j'ai pas tout à  refaire au risque d'oublier un truc), simplifier le codage de tout ça ? Genre
GENERATE_CLASS( Auteur , (NSString,nom),(NSString,prenom),(NSDate,dateNaissance) );<br />// et hop magique ça me génère @interface et @implementation complète avec les variables d&#39;instance citées et leurs types, en créant le init/dealloc/encodeWithCoder/initWithCoder qui vont bien
Bon je rêve peut-être un peu c'est sans doute utopiste, mais si vous avez au moins des astuces... je suis prenneur, vu la taille de ma structure à  générer.


Voilà , si vous avez des idées... elles sont toutes bienvenues ;)
Merci d'avance

PS : C'est pour iPhone, donc pas de CoreData (même si je sais pas si ça aurait été adapté, mais au moins ça coupe court à  la possibilité ^^)

Réponses

  • schlumschlum Membre
    01:17 modifié #2
    dans 1238066617:

    Solutions possibles :
    • un bête NSDictionary : c'est KVC, facilement sérialisable... mais on peut enregistrer un peu n'importe quoi dedans donc côté error-checking des clés c'est pas terrible. Et côté facilement parcourable c'est pas ça : on peut bien faire un [tt][dict valueForKeyPath:@machin.truc.bidule][/tt] mais je préférerais qd mm une notation directe dict.machin.truc.bidule
    • Une classe perso dérivant de NSObject avec les variables d'instance qui vont bien



    Un mix des deux  :P
    Une classe dérivée de NSMutableDictionary qui teste les clés en entrée, avec éventuellement des ajouts de properties pour les clés courantes.
  • ChachaChacha Membre
    mars 2009 modifié #3
    dans 1238066617:

    Voilà , si vous avez des idées... elles sont toutes bienvenues ;)


    Bah à  part les subtilités sur les macros, je vois pas trop. Tu connais le # et le ## ?
    #TOTO génère la chaà®ne "TOTO" au préprocessing
    et ## permet de concétaner.
    et les macros supportent le __VA_ARGS__ http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html

    Rien de bien neuf, mais si on connaà®t pas...

    PS : C'est pour iPhone, donc pas de CoreData (même si je sais pas si ça aurait été adapté, mais au moins ça coupe court à  la possibilité ^^)
    Ah me semble bien que le SDK 3.0 contient du CoreData :-D

    +
    Chacha
  • CéroceCéroce Membre, Modérateur
    01:17 modifié #4
    dans 1238066617:

    PS : C'est pour iPhone, donc pas de CoreData


    Et NSXMLDocument ?
    ça semble répondre à  ton cahier des charges.
  • NoNo Membre
    01:17 modifié #5
    dans 1238069831:

    dans 1238066617:

    PS : C'est pour iPhone, donc pas de CoreData


    Et NSXMLDocument ?
    ça semble répondre à  ton cahier des charges.


    PS : C'est pour iPhone, donc pas de NSXMLDocument.
  • AliGatorAliGator Membre, Modérateur
    mars 2009 modifié #6
    Grand merci pour vos réponses, je pensais pas déchaà®ner les foules pourtant avec ma question :)

    dans 1238068979:

    Un mix des deux  :P
    Une classe dérivée de NSMutableDictionary qui teste les clés en entrée, avec éventuellement des ajouts de properties pour les clés courantes.
    C'est pas idiot... j'y ai pensé à  un moment, mais me suis dit que quand je déclarais mes properties, il faudrait que j'écrive moi-même les setters/getters desdites propriétés pour faire un setValue:forKey et un valueForKey à  la place... donc j'avais oublié cette possibilité. Mais vu comme finalement les autres possibilités impliquent aussi de pisser pas mal de code... finalement ça pouraà®t être un bon compromis.

    Un truc comme ça donc :
    #define PROP_DECL(Type, var) @property(nonatomic, retain, setter=set_##var) Type var;<br /><br />#define PROP_DEF(Type, var) &#092;<br />-(Type)var { return (Type)[self valueForKey:@ #var]; } &#092;<br />-(void)set_##var:(Type)val { [self setValue:val forKey:@ #var]; }<br /><br />@interface Auteur : NSMutableDictionary {}<br />  PROP_DECL( NSString* , nom)<br />  PROP_DECL( NSString* , prenom)<br />  PROP_DECL( NSDate* , dateNaissance)<br />@end<br /><br />@implementation Auteur<br />  PROP_DEF(NSString* , nom )<br />  PROP_DEF(NSString* , prenom )<br />  PROP_DEF(NSDate* , dateNaissance )<br />@end<br /><br />/////////////////////////////////<br />... et les autres (Description, Societe) pareil<br />/////////////////////////////////<br /><br />@interface Logiciel : NSMutableDictionary {}<br />  PROP_DECL( Auteur* , auteur )<br />  PROP_DECL( Description* , description )<br />  PROP_DECL( Societe* , societe )<br />  PROP_DECL( NSString* , plateforme )<br />@end<br /><br />@implementation Logiciel<br />  PROP_DEF( Auteur* , auteur )<br />  PROP_DEF( Description* , description )<br />  PROP_DEF( Societe* , societe )<br />  PROP_DEF( NSString* , plateforme )<br /><br />-(void)init {<br />  if ((self = [super init])) {<br />    self.auteur = [[[[Auteur alloc] init] autorelease];<br />    self.description = [[[Description alloc] init] autorelease];<br />    self.societe = [[[Societe alloc] init] autorelease];<br />  }<br />}<br />@end
    
    J'ai rajouté le init pour pré-créer ma structure de dictionnaires quand même, pour que si je fais [soft setValue:@moi forKeyPath:@auteur.nom] il fonctionne bien (car si auteur est nil au lieu d'être un Auteur* existant, setValue:forKeyPath: marchera pas)... donc si je me suis pas gourré dans mon init, il va me créer un Auteur* (soit un NSMutableDictionary) qu'il va affecter à  la clé "auteur" du dictionaire principal "Logiciel", pareil pour description et societe, ce qui me permettra d'utiliser setValue:forKeyPath: sans soucis...
    Et du coup pas besoin d'implémenter NSCopying, c'est déjà  tout prêt, ni besoin de déclarer des variables d'instance qui font redondant avec les @property puisqu'on utilise un NSDictionary pour les stocker.


    Vous en pensez quoi, de cette solution ? (pas encore testée sur un vrai projet) ?

    Sinon au lieu de faire par héritage, je pensais aussi faire par wrapping : Auteur dériverait de NSObject, mais contiendrait une vi unique NSMutableDictionary* data. Et après on reprend le même principe. Avantage, pas de risque d'appeler les méthodes de NSMutableDictionary sur mon Auteur. Inconvénient, faut implémenter NSCopying, mais au moins il est générique, il suffit d'encoder data pour que ça sérialise tout l'objet, donc c'est "macrotisable".
    Je sais pas lequel est le plus propre...


    En effet sinon le XML c'est pas top car sur iPhone, pas de parsing DOM du XML, que du parsing SAX, donc ça va pas le faire.
  • schlumschlum Membre
    01:17 modifié #7
    Oui, en wrapping c'est limite plus propre effectivement  ;)
  • AliGatorAliGator Membre, Modérateur
    mars 2009 modifié #8
    Hello les gens !

    Alors pour info, voilà  comment j'ai fini par implémenté la chose, en suivant vos conseils :

    Voilà  le début de mon .h (macros et classe de base) :
    <br />#define PROP_OBJ(Type, var) &#092;<br />@property(nonatomic, retain, setter=set_##var:) Type var;<br /><br />#define PROP_BOOL(var) &#092;<br />@property(nonatomic, assign, setter=set_##var:) BOOL var;<br /><br /><br /><br />@interface StructuralObject : NSObject &lt;NSCoding&gt;<br />{<br />	NSMutableDictionary* data;<br />}<br />@end
    


    Et le début de mon .m (macros et classe de base) :
    #undef PROP_OBJ<br />#define PROP_OBJ(Type, var) &#092;<br />-(Type)var { return (Type)[ data valueForKey:@#var ]; } &#092;<br />-(void)set_##var:(Type)val { [ data setValue:val forKey:@#var ]; }<br /><br />#undef PROP_BOOL<br />#define PROP_BOOL(var) &#092;<br />-(BOOL)var { return [(NSNumber*)[ data valueForKey:@#var ] boolValue]; } &#092;<br />-(void)set_##var:(BOOL)val { [ data setValue:[NSNumber numberWithBool:val] forKey:@#var ]; }<br /><br />@implementation StructuralObject<br />- (id) init<br />{<br />	self = [super init];<br />	if (self != nil) {<br />		data = [[NSMutableDictionary alloc] init];<br />	}<br />	return self;<br />}<br />- (id)initWithCoder:(NSCoder *)decoder<br />{<br />	//self = [super initWithCoder:decoder];<br />	data = [[decoder decodeObject] retain];<br />	return self;<br />}<br />- (void)encodeWithCoder:(NSCoder *)coder<br />{<br />	//[super encodeWithCoder:coder];<br />	[coder encodeObject:data];<br />}<br />- (void)setValue:(id)value forUndefinedKey:(NSString *)key<br />{<br />	// I have to implement this because we used a special set_xxx setter for xxx keys (because with #define macros we can&#39;t capitalize first letter to create setXxx, do we ?)<br />	NSString* setter = [NSString stringWithFormat:@&quot;set_%@:&quot;,key]; // the &quot;setter&quot; selector name we expect<br />	if ([self respondsToSelector:NSSelectorFromString(setter)]) // the corresponding setter for key xxx exists, so it&#39;s ok<br />		[data setValue:value forKey:key]; // set the value (same code as if we called set_xxx here)<br />	else<br />		[super setValue:value forUndefinedKey:key]; // the setter really does not exist, don&#39;t allow to set the value<br />}<br />- (void) dealloc<br />{<br />	[data release];<br />	[super dealloc];<br />}<br />-(NSString*)description<br />{<br />	return [data description];<br />}<br />@end
    
    Ces deux outils (les macros et la classe StructuredObject) me permettent de déclarer ensuite mes classes ainsi (dans le même .h et .m en l'occurrence) :
    // à  la suite dans mon .h :<br />@interface Person : StructuralObject  {}<br />	PROP_OBJ( NSString* , name )<br />	PROP_OBJ( NSString* , surname )<br />	PROP_OBJ( NSString* , address )<br />	PROP_OBJ( NSString* , zipCode )<br />	PROP_OBJ( NSString* , country )<br />	PROP_OBJ( NSString* , phoneOrMail )<br />@end<br />// ... et plein d&#39;autres classes du genre<br /><br />// à  la suite dans mon .m :<br />@implementation Person<br />	PROP_OBJ( NSString* , name )<br />	PROP_OBJ( NSString* , surname )<br />	PROP_OBJ( NSString* , address )<br />	PROP_OBJ( NSString* , zipCode )<br />	PROP_OBJ( NSString* , country )<br />	PROP_OBJ( NSString* , phoneOrMail )<br />@end<br />// ... et plein d&#39;autres classes du genre
    
    Avantage, c'est propre, une fois mes macros et ma classe de base StructuralObject prête, toutes mes classes ressemblent à  la classe Person citée ici en exemple... et que comme vous pouvez le constater, c'est exactement le même contenu mis dans le @interface et le @implementation, permettant de faire direct un copier/coller ;) C'est la définition différente des macros dans le .h et le .m qui fait tout le travail.

    ---

    Et en plus ça permet tout de même des cas particuliers : par exemple j'ai une classe EntityGroup (que j'ai simplifiée ici) qui gère entre autres un CGPoint... variable qui ne peut pas être mise dans le dictionnaire. Du coup soit je l'encodais en NSDictionary ou NSValue pour le stocker... soit je mettais une variable d'instance pour ce cas particulier, ce que j'ai fini par faire, De plus cette classe est un bon exemple aussi car elle contient des v.i. elles-mêmes de type StructuralObject (enfin des sous-classes), qu'il faut donc penser à  initialiser :
    @interface EntityGroup : StructuralObject<br />{<br />	CGPoint position;<br />}<br />	PROP_OBJ( Person* , driver )<br />	PROP_OBJ( Vehicle* , vehicle )<br />	@property(nonatomic, assign) CGPoint position; // lui est associé à  la v.i. et non stocké dans le dico<br />@end<br />
    
    Et donc pour l'implémentation :
    @implementation EntityGroup<br />@synthesize position;<br /><br />- (id) init<br />{<br />	self = [super init];<br />	if (self != nil) {<br />		self.driver = [[[Person alloc] init] autorelease];<br />		self.vehicle = [[[Vehicle alloc] init] autorelease];<br />	}<br />	return self;<br />}<br />- (void)encodeWithCoder:(NSCoder *)coder {<br />	[super encodeWithCoder:coder];<br />	[coder encodeObject:[NSValue valueWithCGPoint:position]]; // du coup faut penser à  l&#39;encoder lors de la sérialisation aussi<br />}<br />- (id) initWithCoder:(NSCoder*)decoder {<br />	self = [super initWithCoder:decoder];<br />	position = [(NSValue*)[decoder decodeObject] CGPointValue]; // et à  le décoder lors de la désérialisation puisqu&#39;il n&#39;est pas dans le dico &quot;data&quot;, lui.<br />	return self;<br />}<br /><br />PROP_OBJ( Person* , driver )<br />PROP_OBJ( Vehicle* , vehicle )<br />@end
    
    Voilà , ça m'a l'air pas mal comme solution ! :)

    Gros avantage : c'est très facile à  implémenter, et vu le nombre de classes du genre que j'ai dans ma structure hiérarchique, c'est un point important.
    Quand je veux déclarer un nouveau "groupe de propriétés" à  utiliser dans ma structure, j'écris [tt]@interface MonType : StructuredObject {}[/tt] puis je liste toutes les propriétés que je veux avec [tt]PROP_OBJ(type,var)[/tt], et je termine avec [tt]@end[/tt]... Et ensuite j'ai simplement à  copier/coller exactement les mêmes lignes dans le @implémentation ;) Comme on le voit pour le cas de la classe "Person"  ;)


    Voilà , ça devient du coup bien plus sympathique que ce vers quoi j'étais parti initialement, donc merci de vos suggestions :o

    (PS : Je n'ai pas encore testé le NSCoding même si je l'ai implémenté dans ma classe StructuredObject, faut que je vérifie que ça marche bien)
  • 6ix6ix Membre
    01:17 modifié #9
    Je tombe sur ce sujet qui me paraà®t très intéressant, même quelques mois après la disucssion  ;)

    Avec le recul, que penses-tu de ta manière de faire ? Est-ce qu'elle correspond bien à  ce que tu voulais, est-ce réellement plus pratique ?

    En tout cas je garde l'idée dans un coin, cela peut être très utile si on a beaucoup de classes du genre à  créer.
Connectez-vous ou Inscrivez-vous pour répondre.