[iPhone SDK][Bug] Cash a l'utilisation de stringByAppendingString de NSString

kaseykasey Membre
06:07 modifié dans API UIKit #1
Bonjour,

Débutant en cocoa je viens solliciter votre aide a propos d'un bug récurant que je ne parviens a résoudre.
Je travaille actuellement sur le bout de code tout simple suivant :

s_Buffer = [s_Buffer stringByAppendingString: @"1"];


Celui-ci est placé dans un outlet tout bête appelé depuis un bouton dans l'interface de l'iPhone.
Hors au bout d'un nombre variable de pression de celui-ci mon appli plante inexplicablement Oo

Pourriez vous m'éclairer sur un possible mauvais choix de fonction ou bien pensez vous que le bug proviens du framework d'Apple?

Réponses

  • kaseykasey Membre
    06:07 modifié #2
    Bon ben visiblement j'ai rapidement trouvé une solution de contournement il faut utiliser le code suivant :

    s_Buffer = [[NSString alloc ] initWithFormat:@"%@1", s_Buffer];
    


    Moralité ne pas suivre la doc d'Apple mais les exemples fournis  >:D

    Reste a savoir pourquoi
    stringByAppendingString
    
    plante Oo
  • MalaMala Membre, Modérateur
    06:07 modifié #3
    Je pense que tu n'y connais pas encore grand chose en gestion mémoire sous Obj-C. Est-ce que je me trompe?

    stringByAppendingString retourne une chaà®ne dite autorelease. Son existence n'est donc garantie que dans le cadre de ta méthode.

    avec une méthode init... la c'est toi qui gère la libération mémoire. Donc effectivement ça ne plante plus. Par contre vu ton code c'est la fuite mémoire assurée...
  • MalaMala Membre, Modérateur
    06:07 modifié #4
    dans 1211318164:

    Moralité ne pas suivre la doc d'Apple mais les exemples fournis  >:D

    Il faudrait peut-être juste commencer par le début de la doc Apple pour le coup.  :P

    Ceci dit, bienvenue sur le forum.  :p   :p
  • MalaMala Membre, Modérateur
    06:07 modifié #5
    Mince, le site Projet Omega est en rideau??? Je voulais te mettre un lien vers la gestion mémoire. Le cache est toujours accessible ici...
    http://74.125.39.104/search?q=cache:DI6ugSF78OMJ:www.projectomega.org/article.php?lg=fr&php=oreilly_cocoa9&p=1+objective-c+cgestion+mémoire&hl=fr&ct=clnk&cd=1

    Cela devrait t'apporter les explications qu'il te faut.
  • kaseykasey Membre
    06:07 modifié #6
    Merci pour les explications, oui en effet j'ai beaucoup de mal a saisir comment correctement gérer la mémoire en Obj-C vus que je viens du C++ et que la priorité de mes professeurs en cour n'as pas été de m'apprendre a gérer au mieux celle-ci.

    Sinon pour project-omega ce n'est pas bien grave, j'ai actuellement comme livre de chevet Cocoa par la pratique, je vais relire le passage sur la gestion de la mémoire.

    Par contre une chose, bien que je n'avais pas remarqué que stringByAppendingString soit un autorelease, je sauvegarde bien le contenus retourné par la fonction dans une string avant que celle-ci ne s'efface non? Car s_Buffer est un pointeur de type NSString. Ou bien je suis complètement a coté de la plaque, j'ai un doute sur ma vision de la chose d'un coup  :-\\ ?

    Une dernière chose, dois-je rajouter un petit release quelque part pour éviter une fuite de mémoire avec l'utilisation de :
    s_Buffer = [[NSString alloc ] initWithFormat:@"%@1", s_Buffer];
    

    Car en effet si a chaque fois une sous chaine de mon résultat en cour ce forme est est stocké pour rien, la fuite de mémoire est assurée  :crackboom:-
  • MalaMala Membre, Modérateur
    mai 2008 modifié #7
    dans 1211321532:

    Par contre une chose, bien que je n'avais pas remarqué que stringByAppendingString soit un autorelease, je sauvegarde bien le contenus retourné par la fonction dans une string avant que celle-ci ne s'efface non? Car s_Buffer est un pointeur de type NSString. Ou bien je suis complètement a coté de la plaque, j'ai un doute sur ma vision de la chose d'un coup  :-\\ ?

    Effectivement, il y a un problème de compréhension. Tu n'aurais pas aussi mangé du Java à  tour de bras? ;) Plus sérieusement, s_Buffer est un pointeur (même concept qu'en C ou C++). L'affectation = ne fait que stocker l'adresse de ton objet dans ton pointeur. Maintenant, rien ne t'empêche de le libérer ( [s_Buffer release] ) pour autant ton pointeur restera sur cette zone mémoire mais ne sera plus accessible. Si tu souhaite conserver un objet autorelease, il te faut faire un retain dessus. Du coup, tu reviens à  ta seconde situation ou on retombe sur un problème de fuite.


    dans 1211321532:

    Une dernière chose, dois-je rajouter un petit release quelque part pour éviter une fuite de mémoire avec l'utilisation de :
    s_Buffer = [[NSString alloc ] initWithFormat:@"%@1", s_Buffer];
    

    Car en effet si a chaque fois une sous chaine de mon résultat en cour ce forme est est stocké pour rien, la fuite de mémoire est assurée  :crackboom:-

    On en vient effectivement là . Donc le problème c'est que initWithFormat va créer un nouvel objet. Comme tu l'affectes dans s_Buffer, si celui pointait vers un précédent objet, tu vas en écraser l'adresse et donc perdre le lien vers cet objet. Comment faire alors?

    Solution 1: Passer par un pointeur "tampon"
    <br />...<br />&nbsp; &nbsp; NSString *tmpBuffer = nil;<br /><br />&nbsp; &nbsp; // Y a-t-il déjà  un objet pointé par s_Buffer?<br />&nbsp; &nbsp; if( s_Buffer )<br />&nbsp; &nbsp; {<br />&nbsp; &nbsp; &nbsp; &nbsp; // On conserve l&#39;adresse de l&#39;ancien objet dans notre pointeur tampon<br />&nbsp; &nbsp; &nbsp; &nbsp; tmpBuffer = s_Buffer;<br />&nbsp; &nbsp; }<br /><br />&nbsp; &nbsp; // On recréer une nouvelle NSString à  partir de notre chaà®ne actuelle<br />&nbsp; &nbsp; s_Buffer = [s_Buffer stringByAppendingString: @&quot;1&quot;];<br /><br />&nbsp; &nbsp; // L&#39;objet etant autorelease, on doit lui appliquer un retain pour prendre la main sur <br />&nbsp; &nbsp; //sa gestion memoire (on n&#39;oubliera pas le release dans le dealloc de la classe qui contient<br />&nbsp; &nbsp; // l&#39;objet...)<br />&nbsp; &nbsp; [s_Buffer retain];<br /><br />&nbsp; &nbsp; // Maintenant on peut tranquillement libérer l&#39;ancien objet<br />&nbsp; &nbsp; if( tmpBuffer )<br />&nbsp; &nbsp; {<br />&nbsp; &nbsp; &nbsp; &nbsp; [tmpBuffer release];<br />&nbsp; &nbsp; &nbsp; &nbsp; tmpBuffer = nil;&nbsp; <br />&nbsp; &nbsp; &nbsp; &nbsp; // Note: Un bon reflexe de routard consiste à  prendre l&#39;habitude<br />&nbsp; &nbsp; &nbsp; &nbsp; // de mettre systématiquement son pointeur à  nil après sa libération.<br />&nbsp; &nbsp; &nbsp; &nbsp; // Pourquoi? Dans un projet complexe, cela permet d&#39;éviter une <br />&nbsp; &nbsp; &nbsp; &nbsp; // manipulation maladroite d&#39;un objet qui aurait été libéré (plantage assuré <br />&nbsp; &nbsp; &nbsp; &nbsp; // et cela permet aussi de voir instantanément les pointeurs libérés lorsqu&#39;on<br />&nbsp; &nbsp; &nbsp; &nbsp; // fait une passe en debug pour une traque de fuites.<br />&nbsp; &nbsp; }<br />...<br />
    


    Bien, si cette méthode fonctionne, elle n'est pour le coup ni élégante (beaucoup de code pour pas grand chose) ni efficace (on passe notre temps à  recréer des objets avec stringByAppendingString). Si tu regardes d'un peu plus prêt les classes à  ta disposition en Obj-C, tu vas découvrir que la plupart d'entre elles on un équivalent "mutable": NSMutableString, NSMutableArray, NSMutableDictionary, etc...

    La méthode 2 consiste donc à  préférer un objet modifiable qui correspond bien mieux à  ton besoin ici:
    <br />...<br />&nbsp; &nbsp; if( !s_Buffer )<br />&nbsp; &nbsp; {<br />&nbsp; &nbsp; &nbsp; &nbsp; // Premier passage, s_Buffer n&#39;existe pas encore<br />&nbsp; &nbsp; &nbsp; &nbsp; s_Buffer = [NSMutableString new];<br /><br />&nbsp; &nbsp; &nbsp; &nbsp; // Pas besoin de retain ici puisque &quot;new&quot; fait partie des méthodes<br />&nbsp; &nbsp; &nbsp; &nbsp; // de base qui ne retourne pas un objet autorelease. Pour plus d&#39;infos<br />&nbsp; &nbsp; &nbsp; &nbsp; // voir le lien vers le cache que je t&#39;ai mis dans mon post précédent.<br />&nbsp; &nbsp; }<br />&nbsp; &nbsp; &nbsp; &nbsp; <br />&nbsp; &nbsp; // Et là  on peut ajouter tout ce que l&#39;on veut à  notre chaà®ne...<br />&nbsp; &nbsp; [s_Buffer appendString:@&quot;1&quot;];<br />&nbsp; &nbsp; <br />...<br />
    


    Contrairement au premier code, ici s_Buffer sera toujours le même objet donc plus besoin de quoi que ce soit d'autre.

    En espérant t'avoir un peu éclairer. ;) Prends le temps de bien appréhender tout ça. Cocoa par la pratique est un très bon ouvrage. Si tu as des questions n'hésites pas.





  • MalaMala Membre, Modérateur
    mai 2008 modifié #8
    J'oubliais une troisième possibilité intermédiaire si on veut aller au bout des choses.

    Utiliser la notion d'autorelease au lieu d'un tampon:

    ...
        if( s_Buffer )
        {
            // En passant s_Buffer en autorelease , l'objet sera libéré par le pool de
            // gestion mémoire une fois la méthode terminée.
            [s_Buffer autorelease];
        }

        // Ici pas de problème pour écraser s_Buffer puisqu'on à  passer la main
        // au pool de gestion mémoire qui en a gardé une référence.
        s_Buffer = [s_Buffer stringByAppendingString: @1];

        // Et on termine par le retain obligatoire
        [s_Buffer retain];

    ...


    Cette méthode est relativement light en terme de syntaxe. Par contre, on cré toujours un nouvel objet à  chaque passe ce qui n'est pas optimal.
  • kaseykasey Membre
    06:07 modifié #9
    Et bien merci beaucoup pour tes explications Mala, j'ai vraiment eu beaucoup de mal a saisir lors de mes premières lectures de Cocoa par la Pratique la gestion de la mémoire (visiblement ca n'étais toujours pas très bien rentré B) ). Mais je pense qu'un bon exemple concret m'aide a bien saisir la chose :)

    Je vais méditer ce ceci et adapter mon code en fonction.
    Je t'en donnerais des nouvelles  ;)

    Merci encore pour ta patience :)
Connectez-vous ou Inscrivez-vous pour répondre.