Pb avec NSUnarchiver

14:43 modifié dans API AppKit #1
Bonjour à  tous.

Il m'arrive un problème qui est plutôt bizarre. En effet, j'archive une hiérarchie d'objets de classe CXTreeNode identique à  l'aide d NSArchiver. J'effectue bien évidemment l'inverse à  l'aide de NSUnarchiver.
J'ai doté CXTreeNode des deux méthodes issues du protocole NSCoding.
Les traces m'indiquent que l'archivage se déroulent sans erreur et le désarchivage également.
Le problème intervient dans l'invocation de la méthode suivante

_tree = [NSUnarchiver unarchiveObjectWithFile:inPath];

Le code plante et le débogueur indique

Program received signal:  "EXC_BAD_ACCESS".

La pile d'exécution localise le bug au niveau de [NSUnarchiver dealloc] qui invoque objc_msgSend.

Ce qui est bizarre, c'est que les méthodes de classe créent des instances en destruction automatique. Cette opération intervient après la fin de l'éxécution de l'événement.
Je ne comprend pas pourquoi la destruction intervient ici !

Quelqu'un a une idée. Merci de son aide.

Réponses

  • Philippe49Philippe49 Membre
    14:43 modifié #2
    des pistes peut-être stupides

    As-tu vérifié si inPath est correcte ?
    Utilises-tu des NSKeyedArchiver ou des NSArchiver, as-tu respecté les modèles d'archivage
    "unArchiveObjectWithFile returns nil if the file at path cannot be unarchived." ce qui fait que _tree peut valoir nil ...



  • 14:43 modifié #3
    Merci de ton aide.
    Il n'y a pas de questions stupides lorsque l'on essaye de comprendre un problème si curieux !
    inPath est correct. Le chargement de l'archive s'effectue normalement car les traces dans initWithCoder de CXTreeNode récupèrent les données membres  dans le même ordre que celui de l'archivage, logique car il s'agit d'un flux. Chaque noeud de l'arbre est matérialisé par un objet de cette classe.
    L'archivage s'effectue uniquement avec NSArchiver. J'ai implémenté la méthode encodeWithCoder. Puisque CXTreeNode hérite de NSObject et que cette dernière ne supporte pas le protocole NSCoding, il n'y a donc pas d'appel à  la super classe.
    Quant à  l'instruction, elle plante avant l'assignation à  la variable _tree.
    Je ne comprend pas pouquoi le destructeur est invoqué comme le montre la vue du debogueur.




  • AliGatorAliGator Membre, Modérateur
    14:43 modifié #4
    Tu n'aurais pas (à  mon avis c'est une piste stupide aussi car ça aurait plutôt planté à  l'archivage qu'au désarchivage mais bon) une structure de données qui pose problème par hasard, par exemple une boucle infinie (si c'est un arbre, un noeud qui a pour enfant un de ses ascendants par exemple...) ?
  • 14:43 modifié #5
    [size=10pt]Bonsoir Aligator,

    Effectivement, chaque noeud détient la référence de son parent ainsi que la liste de ses enfants. Il mémorise également une donnée sous la forme d'un NSMutableDictionary ainsi qu'un indicateur précisant s'il peut avoir une descendance. L'encodage est réalisé par la méthode suivante:

    //**********************************************************************************************************<br />// encodeWithCoder<br />//**********************************************************************************************************<br />-(void)encodeWithCoder:(NSCoder*)coder<br />{<br />	[coder encodeConditionalObject:_parent]; // Sorte d&#39;evaluation paresseuse<br />	//NSLog([[_data valueForKey:@&quot;0&quot;] description]);<br />	[coder encodeObject:_data];<br />	[coder encodeObject:_children];<br />	[coder encodeObject:[NSNumber numberWithBool:_group]];<br />} /* encodeWithCoder */<br />
    


    Le décodage est réalisé par la méthode suivante:

    //**********************************************************************************************************<br />// initWithCoder<br />//**********************************************************************************************************<br />-(CXTreeNode*)initWithCoder:(NSCoder*)coder<br />{<br />	self = [super init];<br />	if (self)<br />	{<br />		_parent = [coder decodeObject];<br />		_data = [coder decodeObject];<br />		NSLog([[_data valueForKey:@&quot;0&quot;] description]);<br />		_children = [coder decodeObject];<br />		_group = [[coder decodeObject] boolValue];<br />	}<br />	return self;<br />} /* initWithCoder */<br />
    

    Il s'agit d'un arbre, c'est à  dire un graphe acyclique. Ainsi tout noeud ne peut pas avoir un ascendant comme enfant.

    Ce qui m'obsède, c'est le destructeur. C'est l'inverse de la convention d'Apple !
    Le pire est que tout fonctionne. L'archivage se déroule sans problème. J'obtiens un fichier. Le désarchivage se déroule bien jusqu'à  l'appel de ce dealloc incongru !

    Petite remarque: L'archivage s'effectue en deux passes. j'ai stoppé le processus avant le lancement de la deuxième phase et enregistré le résultat. Il semble que la première passe mémorise la structure (le graphe) tandis que la seconde semble mémoriser les noeuds du graphe ainsi obtenu. Bien évidemment, il faudrait inspecter plus pour certifier.

    Merci de ton aide.[/size]
  • Philippe49Philippe49 Membre
    juin 2008 modifié #6
    1 ) En général, on procède ainsi :
    • Le fils est retenu comme élément de la NSArray des children (attention à  ce qu'il ne soit retenu qu'une fois)
    • Le parent n'est pas retenu par le fils


    2) L'encodage doit concerner aussi super : [super encodeWithCoder:coder];
  • Philippe49Philippe49 Membre
    juin 2008 modifié #7
    Lors du initWithCoder: , il faut également faire les [... retain] nécessaires.

    doc : When to Retain a Decoded Object
    You can decode an object value in two ways. The first is explicitly, using the decodeObject or decodeObjectForKey: method. When decoding an object explicitly you must follow the object ownership convention and retain the object returned if you intend to keep it. Otherwise, the object is owned by the coder and the coder is responsible for releasing the object.

    The second means of decoding an object is implicitly, using the decodeValueOfObjCType:at: method or one of its variants, decodeArrayOfObjCType:count:at: and decodeValuesOfObjCTypes:. These methods decode values directly into memory that you provide. In the case of objects, the value is the object pointer. As this memory is already owned by you, you are responsible for releasing the objects decoded into it. This behavior can prove useful for optimizing large decoding operations, as it obviates the need for sending a retain message to each decoded object.
  • juin 2008 modifié #8
    Bonjour et merci pour l'aide. Il semble que la nouvelle version fonctionne. Je vais poursuivre les vérifs (tester les retainCount).
    En conclusion, il fallait faire:

    • Retenir chaque donnée membre.
    • Retenir l'objet CXTreeNode (self) au niveau de initWithCoder.

    NSUnarchiver alloue temporairement ce qu'il désarchive.
    Tout celà  n'est pas très logique car si l'on veut désarchiver un graphe d'objets, c'est pour travailler dessus sans limite de durée de vie des objets réanimés.
    Les méthodes de classe évite de se préoccuper de la destruction de l'objet utilisé et pas des objets impliqués. Dans notre cas, il s'agissait de NSUnarchiver et non pas du graphe.

    Et encore merci à  Philippe49 et Aligator.
  • Philippe49Philippe49 Membre
    juin 2008 modifié #9
    dans 1213685280:

    • Retenir l'objet CXTreeNode (self) au niveau de initWithCoder.

    Je n'ai jamais vu faire [self retain] dans un init.

    As-tu rajouté [super encodeWithCoder:coder], c'est nécessaire pour que l'archive contienne les données de la classe parente de l'objet.

    dans 1213685280:

    Tout celà  n'est pas très logique car si l'on veut désarchiver un graphe d'objets, c'est pour travailler dessus sans limite de durée de vie des objets réanimés.

    Oui et non, c'est la stratégie générale d'initialisation qui est comme ça. Quand on initialise un champ dans une méthode -(id) init, il faut bien faire le retain, et pourtant on veut bien travailler avec après.
  • 14:43 modifié #10
    Bonjour Philippe,

    En ce qui concerne le [self retain], bien évidemment cet appel est absude! Une over dose de chocolat sans aucun doute pour avancer le projet !
    Le seul noe“ud a retenir est le noe“ud racine ce qui s'écrit simplement par:

    _tree = [[NSUnarchiver unarchiveObjectWithFile:inPath] retain];
    


    Le second point concerne les appels des méthodes de la super classe. Effectivement , l'archivage d'un objet consiste à  enregistrer uniquement les données membre de l'objet en question. Si l'objet hérite d'une classe supportant le protocole NSCoding, il faut effectivement invoquer la méthode correspondante.
    Dans notre cas, CXTreeNode hérite de NSObject. Cette dernière ne supporte pas le protocole donc, il n'y a pas d'appel supplémentaire.
    Ce principe s'applique également à  NSUnarchiver.

    J'ai retesté le code dans son ensemble et tout fonctionne. Les objets dans la hiérarchie ont tous leur compteur de retenue à  1 à  la fois pour l'archivage et le désarchivage.

    Encore merci, Philippe pour ton aide.
  • Philippe49Philippe49 Membre
    juin 2008 modifié #11
    dans 1213782516:

    Effectivement , l'archivage d'un objet consiste à  enregistrer uniquement les données membre de l'objet en question. Si l'objet hérite d'une classe supportant le protocole NSCoding, il faut effectivement invoquer la méthode correspondante.
    Dans notre cas, CXTreeNode hérite de NSObject. Cette dernière ne supporte pas le protocole donc, il n'y a pas d'appel supplémentaire.


    Ok merci de rappeler ce point.(Je pensais plus ou moins confusément que CXTreeNode héritait de NSTreeNode)
Connectez-vous ou Inscrivez-vous pour répondre.