Message d'erreur du à  NSTextFieldCell

VeillardVeillard Membre
juillet 2004 modifié dans API AppKit #1
Bonjour,

Je suis nouveau sur la liste et dans la programmation en Cocoa. J'ai commencé à  traduire une appli. je j'ai développé en Carbon et je me heurte à  quelques difficultés.

Dans un premier temps, je cherche à  saisir des données sous forme d'entiers ou de "strings" dans des "NSTextField" pour l'instant tout fonctionne.
Dans un second temps, je veux que ces valeurs réapparaissent dans ces "NSTextFieldCell"  ce qui fonctionne très bien pour les entiers mais pas pour les "string".

A l'exécution de l'appli, quand je clique sur mon bouton que j'ai appelé "Editer", j'ai le message suivant qui apparaà“t dans la console d'Xcode :

2004-07-11 09:49:23.428 CocoaApp[416] *** Assertion failure in -[NSTextFieldCell _objectValue:forString:], AppKit.subproj/NSCell.m:1131
2004-07-11 09:49:23.444 CocoaApp[416] Invalid parameter not satisfying: aString != nil

A quel endroit ai-je fait un erreur ?

Merci

Réponses

  • mpergandmpergand Membre
    16:02 modifié #2
    Tu nous ne donnes pas un exemple de code et c'est pas facile de savoir ce que tu fais exactement.
  • VeillardVeillard Membre
    16:02 modifié #3
    Voici un fragment du code en question...

    @implementation EditController

    SInt32 currentDive, seekDive;

    struct carnet  logbk[MAX_NB_DIVE ];

    - (IBAction)modifier:(id)sender                      /* Modification du carnet de plongees */
    {
    NSString *notes = [[NSString alloc] init];
    notes = [notesField stringValue];

    currentDive = [plongeeNoField intValue];

    logbk[currentDive].coeff = [coeffField intValue];
    logbk[currentDive].date = [dateField stringValue];
    logbk[currentDive].heure = [heureField stringValue];
    logbk[currentDive].lieu = [lieuField stringValue];
    logbk[currentDive].site = [siteField stringValue];
            /* etc... avec des string */
    }

    - (IBAction)editOK:(id)sender                      /* Editer les plongees */
    {
    seekDive = [seekDiveField intValue];

    [plongeeNoField setIntValue:seekDive];
    [coeffField setIntValue:logbk[seekDive].coeff];
    [dateField setStringValue:logbk[seekDive].date];
    [heureField setStringValue:logbk[seekDive].heure];
    [lieuField setStringValue:logbk[seekDive].lieu];
            [siteField setStringValue:logbk[seekDive].site];
            /* etc... */

            [notesField setStringValue:notes];
    }

    @end




    Mes variables font partie de la structure suivante :

    Fichier "struct.h"

    struct carnet
    {
    SInt32 nbPlongee, coeff;
     
    NSString *date, *heure, *lieu, *site;
            /* etc avec les autres string */
    };


    Le choix d'utiliser une structure mer permettra dans un premier temps de récupérer toutes les plongées que j'ai enregistré avec mon appli sous Carbon (env. 600...).


    Voilà

    A+
  • ClicCoolClicCool Membre
    16:02 modifié #4
    Je me demande si par hasard tes strings récupérées pour tes structs n'ont pas été désallouées par la suite ...
    Les NSString, à la différence des foat etc... sont des objets dont tu ne récupères que les pointeurs.
    Ta struct ne contiens donc pas vraiment les chaines mais juste des pointeurs.

    Si tu veux garantire la validité de tes struct mieux vaux "retenir" les objets comme les NSSTrings
    logbk[currentDive].date = [[dateField stringValue] retain];
    

    Et bien sur les libérer quand tu te débarasse de la struct
    [currentDive].date release]
    
  • mpergandmpergand Membre
    juillet 2004 modifié #5
    Bon, y a plusieurs choses curieuses dans ton code:

    NSString *notes = [[NSString alloc] init];
    ††notes = [notesField stringValue];

    stringValue retourne un NSString, pas besoin d'initialiser notes, en plus tu as une fuite mémoire :)

    dans editOk, notes y sort d'où ? il est initialisé comment?
  • VeillardVeillard Membre
    16:02 modifié #6
    Ca marche avec "retain" !  :)  Merci.

    Pour répondre à mpergand, j'ai supprimé l'initialisation de "notes" => OK
    En réalité concernant la sortie de "notes", je l'avais sorti de la structure pour faire des tests. Je l'ai réintégré dans la structure et tout fonctionne normalement. Pour info, "notes" sortait dans un NSTextField.

    Concernant l'utilisation de "release" comme me l'a suggéré ClicCool, j'hésite à l'utiliser parce que j'aimerais conserver toutes mes données en mémoire jusqu'au prochain enregistrement ou création d'un nouveau carnet.

    Merci à tous

    A+
  • ClicCoolClicCool Membre
    16:02 modifié #7
    dans 1089541988:

    Concernant l'utilisation de "release" comme me l'a suggéré ClicCool, j'hésite à l'utiliser parce que j'aimerais conserver toutes mes données en mémoire jusqu'au prochain enregistrement ou création d'un nouveau carnet.


    Si c'est ta struct qui garde la référence à ton objet string, ben t'as pas à t'en soucier, tu n'envoies un release à tes strings que quand tu VEUX que la struct soit détruite.

    Si tu comptes garder ailleurs une référence à tes string, il faut quand même envoyer un release à la destruction de ta struct.
    C'est l'objet qui doit garder une référence à tes strings qui est responsable d'elles et doit envoyer aussi un retain à tes objets strings au moment où elle en reçoit la référence. A son tour cet objet enverra un release lorsqu'il n'en aura plus besoin ...

    Les retain-release sont comptabilisés.
    Chaque objet qui compte garder une référence à un autre objet doit envoyer un retain à l'objet référencé.
    Par la suite il DOIT envoier un release quand il "làche la référence".
    Tant que le "retainCount" est superieur à zéro, l'objet reste retenu et ne sera pas désalloué.
    Quand TOUS les objets ayant gardé une référence sur cet objet auront envoyé un release, le compte des retains tombe à zéro et l'objet est désalloué car plus personne n'en veut.
  • VeillardVeillard Membre
    16:02 modifié #8
    Merci pour tes explications. Je comprends mieux comment cela fonctionne.

    A+
  • Eddy58Eddy58 Membre
    16:02 modifié #9
    Règle simple : Il faut 1 Retain pour 1 Release.....et là plus de soucis....  :)
  • ClicCoolClicCool Membre
    16:02 modifié #10
    dans 1089582760:

    Règle simple : Il faut 1 Retain pour 1 Release.....et là plus de soucis....† :)

    ou versa-vice† ;D
    J'aurais plutôt écrit:
    Il faut 1 Release pour pour chaque Retain

    En comptant les [[objet alloc] init] qui renvoient un objet avec un retainCount à 1
    Contrairement aux constructeurs de commodités qui renvoient un objet "autoreleasé" avec un retainCount à zéro.
  • 16:02 modifié #11
    dans 1089584186:

    Contrairement aux constructeurs de commodités qui renvoient un objet "autoreleasé" avec un retainCount à zéro.


    Attention, le retain count d'un objet autoreleasé est égal à 1, mais comme l'objet est placé dans l'autorelease pool, il sera automatiquement releasé "plus tard". Quand un objet atteind un retain count de 0, il est désalloué (un dealloc lui est envoyé).
  • ClicCoolClicCool Membre
    16:02 modifié #12
    Oups ! En effet

    La différence, pour subtile qu'elle paraisse, est notable !

    Merci† Arldon ;)
  • ClicCoolClicCool Membre
    16:02 modifié #13
    dans 1089618371:

    Attention, le retain count d'un objet autoreleasé est égal à 1

    Tant qu'à être précis: ;)

    Si cela est vrai pour un objet tout juste renvoyé par un constructeur de commodité, c'est moins certain pour tout objet auquel nous adressons un autorelease.
    Si on envoie un autorelease à un objet dont le retainCount est supérieur à 1, il me semble que l'autorelease ne fera que "programmer" l'envoie d'un seul Release, le compteur restera alors supérieur à zéro.
    Non ?
  • mpergandmpergand Membre
    16:02 modifié #14
    A ce propos:

    lorsque l'on fait:

    NSString* maStr=[ monObj stringValue];

    obtient-on une copie, si oui est-elle en autorelease† ? Est-ce toujours vrai ?

    Et si je fais:

    // si maStr à un retain count de 1
    NSMutableArray* array=[[NSMutableArray]alloc]init];
    [array addObject: maStr];† // retain count =2 (+1)
    ...
    [array removeObject: maStr];† // retain count=1 (-1)

    et maStr n'est jamais désalloué !

    Donc ne pas oublier de faire un autorelease sur maStr !

    C'est bien compliqué cette histoire de mémoire en ObjectiveC et je suis convaincu que la plupart des applis ont des bugs à ce niveau ( ex: XCode, voir les messages dans la console)
  • Eddy58Eddy58 Membre
    16:02 modifié #15
    ou versa-vice†
    J'aurais plutôt écrit:
    †Il faut 1 Release pour pour chaque Retain


    oui tout à fait, c'est plus  logique dans cet ordre :)


    lorsque l'on fait:

    NSString* maStr=[ monObj stringValue];

    obtient-on une copie, si oui est-elle en autorelease† ? Est-ce toujours vrai ?


    Tout dépend si tu fais :

    NSString *maStr=[[NSString alloc] init]; // maStr est ici déclaré et initialisé au niveau local. Ca te fait un retain de 1 : A toi de gérer sa      ______________________________libération.
    maStr=[monObj stringValue];

    Il te faudra faire un [maStr release]; dans ta méthode quand tu n'auras plus besoin de maStr

    Si tu fais :

    NSString *maStr=[NSString stringWithString:[monObj stringValue]]; // maStr est initialisé en mode temporaire. Tu n'as pas à te soucier de            _____________________________________________________sa libération

    Si tu fais :
    NSString *maStr; // Déclaré en mode global dans ton fichier d'interfaçage (.h)

    Ensuite dans ton implémentation (.m), dans la méthode voulue :
    maStr=NSString stringWithString:[monObj stringValue retain]; // Tu as besoin de maStr dans une autre méthode donc tu fais un retain

    Dans ce cas dans ta méthode -(void)dealloc:  il te faudra faire un [maStr release];

    ....1 release pour 1 retain :)

  • Eddy58Eddy58 Membre
    16:02 modifié #16
    Et si je fais:

    // si maStr à un retain count de 1
    NSMutableArray* array=[[NSMutableArray]alloc]init];
    [array addObject: maStr];† // retain count =2 (+1)
    ...
    [array removeObject: maStr];† // retain count=1 (-1)

    et maStr n'est jamais désalloué !

    Donc ne pas oublier de faire un autorelease sur maStr !

    C'est bien compliqué cette histoire de mémoire en ObjectiveC et je suis convaincu que la plupart des applis ont des bugs à ce niveau ( ex: XCode, voir les messages dans la console)


    Il faut savoir qu'un objet de type "collection" (NSMutableArray, NSMutableDictionary, etc...) gère lui même les objets que l'on stocke dedans. Il s'occupera donc de libérer tout les objets qui sont dedans quand lui-même sera libéré.

    Donc dans ton cas, après [array addObject: maStr];
    Tu fais un [maStr release];
    Et tu retombes sur tes pattes.....
  • 16:02 modifié #17
    Une petite clarification, il y a une règle qui dit
    - on ne s'occupe pas de la gestion en mémoire d'un objet qu'on a pas créé soit même.

    Donc dans le cas de [monObjet stringValue];, la NSString renvoyée doit être dans l'autorelease pool, si elle a été créé spécialement pour l'occasion. Si elle ne l'est pas, c'est qu'elle est retenue ailleurs. Donc on ne s'occupe pas de faire des release dessus, à moins d'avoir fait excplicitement un retain dessus.

    Donc c'est une règle qu'il faut respecter dans le design de ses objets.

    Et il y a une autre règle qui dit que si le nom de la classe renvoyée est inclus dans le nom d'une méthode (de classe ou d'instance), on n'a pas à se soucier de le releaser en tant voulu. De nouveau on fait un retain pour s'assurer que l'objet sera bien dispo si on en a besoin de manière plus permanente.
  • ClicCoolClicCool Membre
    16:02 modifié #18
    Tout dépend si tu fais :

    NSString *maStr=[[NSString alloc] init]; // maStr est ici déclaré et initialisé au niveau local. Ca te fait un retain de 1 : A toi de gérer sa† † † ______________________________libération.
    maStr=[monObj stringValue];

    Il te faudra faire un [maStr release]; dans ta méthode quand tu n'auras plus besoin de maStr


    Je ne suis pas sur d'être d'accord avec toi sur ce point. ;)

    Avec: NSString *maStr = [[NSString alloc] init]
    Tu instancies un objet NSString avec un retainCount à 1 en effet
    et tu concerve un pointeur dessus avec maStr.
    Mais si après tu fais:
    maStr = [monObj stringValue];
    Tu change la valeur de ton pointeur (et pas de ton objet string précedemment initialisé) pour qu'il pointe sur la NSString renvoyée par stringValue.

    Tu perds du même coup toute référence à la string initialisée dans la ligne précédente.
    Tu te retrouves donc avec un objet fantome sans avoir gardé de référence dessus pour le releasé !
  • mpergandmpergand Membre
    juillet 2004 modifié #19
    Il faut savoir qu'un objet de type "collection" (NSMutableArray, NSMutableDictionary, etc...) gère lui même les objets que l'on stocke dedans. Il s'occupera donc de libérer tout les objets qui sont dedans quand lui-même sera libéré.

    le terme 'libérer' est ambigu ici, car il sous-entend que les objets seront désalloués, ce qui n'est vrai que s'il ont un retain de 1.
  • Eddy58Eddy58 Membre
    16:02 modifié #20

    Je ne suis pas sur d'être d'accord avec toi sur ce point.

    Avec: NSString *maStr = [[NSString alloc] init]
    Tu instancies un objet NSString avec un retainCount à 1 en effet
    et tu concerve un pointeur dessus avec maStr.
    Mais si après tu fais:
    maStr = [monObj stringValue];
    Tu change la valeur de ton pointeur (et pas de ton objet string précedemment initialisé) pour qu'il pointe sur la NSString renvoyée par stringValue.

    Tu perds du même coup toute référence à la string initialisée dans la ligne précédente.
    Tu te retrouves donc avec un objet fantome sans avoir gardé de référence dessus pour le releasé !


    Oui je suis d'accord avec le fait que tu ne sois pas d'accord ;)
    En effet j'ai fait une bourde.....
    J'aurais du écrire :

    NSString *maStr = NSString alloc] initWithString:[monObj stringValue;

    Voilà.... :)
  • Eddy58Eddy58 Membre
    16:02 modifié #21
    le terme 'libérer' est ambigu ici, car il sous-entend que les objets seront désalloués, ce qui n'est vrai que s'il ont un retain de 1.


    libérer == releaser

    Si tu met un objet NSString dans un NSMutableArray par exemple, son retainCount sera obligatoirement à 1 puisqu'un objet avec un retainCount de 0 n'a plus raison d'être et est donc releasé..... Quand tu releaseras ton NSMutableArray, celui-ci s'occupera de releaser tout les objets qu'il comporte.....

    NSString *maStr=[monObj stringValue]; // retainCount=1
    NSMutableArray *monArray=[[NSMutableArray] alloc] init];
    [monArray addObject:maStr]; // retainCount=1 (dans le NSMutableArray)
    [maStr release]; // retainCount=0
    [monArray release]; // retainCount=0
  • ClicCoolClicCool Membre
    16:02 modifié #22
    Ben, je sais pas pour vous, mais moi je trouve très sympathiques ces échanges façon "joutes" amicales.
    Je trouve ça très enrichissant et j'espère que c'est instructif aux lecteurs et à ce titre souhaite en voir d'autres voir le jour† :D
    A mon sens c'est POSITIF

    Merci (dans l'orde d'apparition sur scene :P ) à Favouille84 qui a ouvert ce fil de discution (bienvenu à toi dans le monde Cocoa), à mpergand dont les compétences ne sont plus à démontrer et qui mine de rien argumente en douce pour Java ;), à Eddy58 qui nous a aidé à souligner plus encore les subtilités de la gestion de la mémoire, à Arldon enfin, dont les avis sont toujours impécablements rigoureux et bien placés.

    MERCI Objective-Cocoa et Isostar de nous fournir ce terrain fertle à l'expression et la progression de tous† :D :D :D :D :D

    Voilà, c'était ma minute entousiaste pour la communauté Objective-Cocoa† :-*

    Favouille84 tu peux reprendre posséssion de ton fil si tu veux ;)
  • muqaddarmuqaddar Administrateur
    16:02 modifié #23
    J'ai lu cette discussion vite fait, mais je vais y revenir très rapidement, dès que je peux me remettre à programmer.
    C'est effectivement très instructif.
    Merci à tout le monde. ;-)
  • 16:02 modifié #24
    dans 1089621116:

    dans 1089618371:

    Attention, le retain count d'un objet autoreleasé est égal à 1

    Tant qu'à être précis: ;)

    Si cela est vrai pour un objet tout juste renvoyé par un constructeur de commodité, c'est moins certain pour tout objet auquel nous adressons un autorelease.
    Si on envoie un autorelease à un objet dont le retainCount est supérieur à 1, il me semble que l'autorelease ne fera que "programmer" l'envoie d'un seul Release, le compteur restera alors supérieur à zéro.
    Non ?


    Oups je n'avais pas vu celui là... La réponse est oui. Autorelease décrémente le retain count de 1 (de manière décalée). On peut donc sur un même objet combiner les autorelease et release pour peu que le retain count ne passe jamais en dessous 0.

    Bon tant qu'à faire un peu de pub. J'avais rédigé à l'époque un tuto sur la gestion de la mémoire, publié ici
  • ClicCoolClicCool Membre
    16:02 modifié #25
    dans 1089662065:

    Bon tant qu'à faire un peu de pub. J'avais rédigé à l'époque un tuto sur la gestion de la mémoire, publié ici

    En effet, j'ignorais jusqu'ici tes talents didactiques† ;D
    Ton Tuto est très bien et il† complète admirablement notre discussion :)
    mpergand tu l'as lu j'imagines? tu reste plus favorable au java quand même ?  :P ;D
  • Eddy58Eddy58 Membre
    16:02 modifié #26
    En effet Arldon, joli tes articles sur P:O ! :) Beau boulot !
  • 16:02 modifié #27
    Merci pour les remerciements ;)

    Comme mes tutos sont retativement bien planqués dans la hiérachie de P:O (ils ne sont pas avec la série connue de leurs tutos d'initiation à la programmation (qui est réservée aux traductions de oreilly), je vais ouvrir un nouveau post dans la section ad hoc et y mettre les références des autres.
  • muqaddarmuqaddar Administrateur
    16:02 modifié #28
    Moi, je me demandais si je n'ouvrais pas un forum sur la gestion de la mémoire.
    Qu'en pensez-vous ?
  • juillet 2004 modifié #29
    Pour moi ça ne vaut pas la peine. Les problèmes abordés en matière de la gestion de la mémoire sont en fait toujours les mêmes, et dès qu'on arrive à un de ces problèmes on ressort les mêmes explications (expérience constatée sur d'autres forums). Je serais plus partant pour un sujet épinglé où donne des explications et des références, et quand on soupçonne la gestion de la mémoire on renvoie à ce sujet.
  • muqaddarmuqaddar Administrateur
    16:02 modifié #30
    Je viens de scinder le sujet en 2 pour ne pas polluer le thread de favouille.
    Le sujet sur java et obj-C est là :
    http://www.objective-cocoa.org/index.php?topic=98
Connectez-vous ou Inscrivez-vous pour répondre.