NSObject -copy

NiClouNiClou Membre
mars 2015 modifié dans Objective-C, Swift, C, C++ #1

Hello,


J'aimerais savoir quelle est la différence entre:



NSArray* array = [[anotherArray copy] autorelease];

et



NSArray* array = anotherArray;

De tête je dirais que l'un copie uniquement l'adresse mémoire de l'autre. Et que l'autre copie l'objet en lui même.


Vos avis svp?


 


Par ailleurs, est ce que assert(e) (assert.h), lorsque la condition est remplie crash l'app? Même en compilation en mode Production? 


Réponses



  • De tête je dirais que l'un copie uniquement l'adresse mémoire de l'autre. Et que l'autre copie l'objet en lui même.


    Vos avis svp?




     


    Tout juste

  • yoannyoann Membre

    copy, comme son nom l'indique, permet de faire une copie de l'objet, un clone.


     


    Après un copy tu as donc deux objets identique en mémoire alors que sur ta seconde ligne tu as simplement deux variable qui pointent sur le même objet.


     


    Faire des copy est utile pour faire ce que l'on appel des copies de protection. Les objets Cocoa étant dans la heap, ils sont partagé. Si tu souhaite garantir qu'une de tes propriété mutable n'est pas modifiable de l'extérieur, tu ne renvois pas cette propriété mais une copie.


     


    ça va avec toutes les notions de passage par valeur ou par référence.


  • CéroceCéroce Membre, Modérateur
    mars 2015 modifié #4

    Un NSArray étant non-mutable, est-ce que -copy ne renvoie pas tout simplement la même adresse ? (j'avoue ne pas avoir testé).


    À quoi bon créer une copie d'un objet non-mutable ?


  • CéroceCéroce Membre, Modérateur

    Je viens de tester:



    -(void) testCopyNSArray {
    NSArray *array = [NSArray arrayWithObjects:@A, @B, @C, nil];
    NSArray *copy = [array copy];
    XCTAssertEqual(array, copy);
    }

    Le test passe. Ce qui signifie que 'array' et 'copy' on la même adresse.


  • CéroceCéroce Membre, Modérateur

    Deuxième test, dont le résultat est plus évident:



    -(void) testCopyNSMutableArray {
    NSMutableArray *mutableArray = [NSMutableArray arrayWithObjects:@A, @B, @C, nil];
    NSArray *copy = [mutableArray copy];
    XCTAssertNotEqual(mutableArray, copy);
    }

    (Passe également)


     


    -copy renvoie un objet non-mutable, donc on s'attend à  ce que les objets soient différents.


  • Merci pour vos lumières. C'est bien ce que je pensais.

    Je faisais un -copy de protection dans mon code aussi et comme j'avais un crash j'avoue que je me suis posé la question.


    Mais enfaite tout venait d'un assert à  la place d'un NSAssert ^^


  • MalaMala Membre, Modérateur
    mars 2015 modifié #8

    Pour culture, attention à  certains côtés piégeux de copy notamment avec des objets mutables. Par défaut, copy sur un NSArray ne fait que réutiliser les objets du premier NSArray en incrémentant le compteur de référence des objets à  dupliquer.


     


    Donc si je fais une copie d'un tableau d'objets mutables (ex: des NSMutableString) je me retrouve avec les mêmes NSMutableString dans le second (même objet=même adresse). Toute modification d'une NSMutableString d'un tableau se reporte alors dans l'autre. Voir ce petit exemple...



    NSMutableArray *myArray = [NSMutableArray new];
    [myArray addObject:[NSMutableString stringWithString:@yes]];
    [myArray addObject:[NSMutableString stringWithString:@no]];
    [myArray addObject:[NSMutableString stringWithString:@perhaps]];
    NSLog(@myArray:%d,(int)myArray);
    for(int num=0; num<[myArray count]; num++)
    {
        NSLog(@%@ -> %@ -> %d",[myArray objectAtIndex:num],[[myArray objectAtIndex:num] className],(int)[myArray objectAtIndex:num],nil);
    }

    // Duplication du tableau    
    NSMutableArray *myArray2 = [myArray copy];

    // Modification après coup de la première NSMutableString du premier tableau
    [[myArray objectAtIndex:0] appendString:@ - titi];
        
    NSLog(@---);
    NSLog(@myArray2:%d,(int)myArray2);
    for(int num=0; num<[myArray2 count]; num++)
    {
        NSLog(@%@ -> %@:%d",[myArray2 objectAtIndex:num],[[myArray2 objectAtIndex:num] className],(int)[myArray2 objectAtIndex:num],nil);
    }

    Les logs résultant...



    2015-03-17 14:32:37.514 TestArray[52356:303] myArray:316240


    2015-03-17 14:32:37.515 TestArray[52356:303] yes -> __NSCFString -> 523456

    2015-03-17 14:32:37.515 TestArray[52356:303] no -> __NSCFString -> 506880

    2015-03-17 14:32:37.515 TestArray[52356:303] perhaps -> __NSCFString -> 493504

    2015-03-17 14:32:37.515 TestArray[52356:303] ---

    2015-03-17 14:32:37.515 TestArray[52356:303] myArray2:315184

    2015-03-17 14:32:37.516 TestArray[52356:303] yes - titi -> __NSCFString:523456

    2015-03-17 14:32:37.516 TestArray[52356:303] no -> __NSCFString:506880

    2015-03-17 14:32:37.516 TestArray[52356:303] perhaps -> __NSCFString:493504


    Céroce, d'après mon exemple copy ne renvoie pas un objet avec la même adresse. Je pense que XCTAssertNotEqual doit tester la similitude des objets contenus.


     


    Si on veut une vraie copie indépendante on doit en passer par un "deep copy" qui va faire une vraie duplication des items...



    NSArray *myArray2 = [[NSArray alloc] initWithArray:myArray copyItems:YES];

  • CéroceCéroce Membre, Modérateur
    mars 2015 modifié #9


     


    Céroce, d'après mon exemple copy ne renvoie pas un objet avec la même adresse. Je pense que XCTAssertNotEqual doit tester la similitude des objets contenus.




    Non, XCTAssertEqual() compare directement les valeurs; ici les pointeurs, en l'occurence.


    Alors que XCTAssertEqualObject() fait appel à  -isEqual:.


     


    Relis ce que j'ai écrit, tu es dans le deuxième cas: -[NSMutableArray copy].


  • MalaMala Membre, Modérateur

    Autant pour moi, oui tu as raison.




  • Si on veut une vraie copie indépendante on doit en passer par un "deep copy" qui va faire une vraie duplication des items...



    NSArray *myArray2 = [[NSArray alloc] initWithArray:myArray copyItems:YES];



     


    Ce n'est pas vraiment une copie profonde "deep copy", enfin ce en est une mais juste pour le premier niveau des objets. Si on veut vraiment une "deep copy" de tous les objets à  tous les niveaux Apple préconise d'archiver et de désactiver la collection.



     


  • CéroceCéroce Membre, Modérateur


    Autant pour moi, oui tu as raison.




    Pas de souci, moi-même, je n'en étais pas sûr.


  • En gros :


     


    copy/copyWithZone : copie superficielle des objets. L'instance de la collection devient immutable et son contenu garde sa mutabilité ancienne (à  tous les niveaux de profondeur de la collection).


     


    initWithArray:copyItems: ( copyItems à  NO ) :  Si tu fait un init sur objet immutable tu aura une classe immutable, si tu le fais sur une classe mutable t'aura une classe mutable. Tous le contenu de la collection garde sa mutabilité ancienne.( à  tous niveaux de profondeur).



     


    initWithArray:copyItems: ( copyItems à  YES ) : Pour la collection elle même, c'est la même chose que précédemment. Par contre c'est différent pour son contenu. Les objets de premier niveaux vont devenir immutable et le reste ( niveaux plus profonds ) vont garder leur mutabilité ancienne.


     


    Archiver/Désarchiver une collection : Tous les objets de tous les niveaux vont garder leur mutabilité ancienne. 



  • NSMutableArray *myArray = [NSMutableArray new];

    // Duplication du tableau    
    NSMutableArray *myArray2 = [myArray copy];



    un copy/copyWithZone : renvoie une instance immutable. Donc c'est une erreur ta dernière ligne et si je ne dis pas de connerie ton code crachera si tu fais par exemple :



    [myArray2 removeLastObjet];

    Parce que l'instance est immutable.

  • AliGatorAliGator Membre, Modérateur
    Le fait que "copy" ne fasse en fait qu'un retain sous le capot quand l'objet est immutable est un détail d'implémentation. En pratique il ne faut pas se baser sur cet aspect là  pour notre réflexion et notre écriture de code. Si on veut s'assurer d'avoir un duplicata indépendant de l'objet, on appelle "copy", point barre.
    Cette logique marchera que l'objet ait une optimisation interne de par son immutabilité ou pas, il ne faut pas avoir à  se poser la question.


    Exemple :
    static NSString* const kArrayKey = @ArrayKey;

    - (NSDictionary*)paramDictionary
    {
    NSMutableArray* array = [NSMutableArray arrayWithObjects:@1,@2,@3,nil];
    return @{ kArrayKey: array };
    }

    - (NSArray*)paramArray
    {
    NSArray* array = [self paramDictionary][kArrayKey];
    return array; // <<--- problem
    }
    Ici on pourrait se dire en lisant juste la méthode paramArray que "on veut renvoyer une copie, mais comme c'est un NSArray et que copy ne fait rien sur les classe immutable, c'est pas la peine de s'embêter à  appeler copy explicitement"... Alors que bien évidemment c'est faux.

    Donc il faut pas se poser la question et se dire "ça sert à  rien d'appeler copy quand la classe est immutable". On veut renvoyer à  l'extérieur (dans l'API publique) une copie de l'objet interne, donc on veut une copie pour être sûr de ne pas juste renvoyer un pointer et que l'extérieur puisse modifier les données internes, donc on appelle "copy", point barre. Objet qui semble immutable ou pas.




    L'important c'est le concept, pas l'implémentation. Conceptuellement / Fonctionnellement on veut une copie on en fait une, faut pas se baser sur comment c'est implémenté en interne. Ne jamais se baser sur des détails d'implémentation.
  • MalaMala Membre, Modérateur



    [myArray2 removeLastObjet];

    Parce que l'instance est immutable.




    Yep, bien vu Samir. Je me flagelle de ce pas...  >:)


     


    Pour l'archivage préconisé par Apple, je trouve ça assez lourd dans la pratique. Tous les objets stockés doivent alors se conformer au protocole NSCoding.

  • MalaMala Membre, Modérateur


    L'important c'est le concept, pas l'implémentation. 




    Je ne partage pas cette opinion. Comprendre l'implémentation d'un mécanisme est tout aussi intéressant et important que le concept lui même.



  • Pour l'archivage préconisé par Apple, je trouve ça assez lourd dans la pratique. Tous les objets stockés doivent alors se conformer au protocole NSCoding.




     


    Je pense que j'ai mal choisi le mot "préconisé". Je voulais dire par la que c'est la seule méthode d'obtenir une "deep copy" de la collection ( de tous son contenu de tous les niveaux).

  • samirsamir Membre
    mars 2015 modifié #19


    On veut renvoyer à  l'extérieur (dans l'API publique) une copie de l'objet interne, donc on veut une copie pour être sûr de ne pas juste renvoyer un pointer et que l'extérieur puisse modifier les données internes, donc on appelle "copy", point barre. 




     


    Pas d'accord avec toi :)


     


    Prenons ton exemple :



    static NSString* const kArrayKey = @ArrayKey;
    - (NSDictionary*)paramDictionary
    {
    NSMutableArray *testArray = [[NSMutableArray alloc] initWithObjects:@1, nil];
    NSMutableArray* array = [NSMutableArray arrayWithObjects:testArray,nil];
    return @{ kArrayKey: array };
    }

    - (NSArray*)paramArray
    {
    NSArray* array = [self paramDictionary][kArrayKey];
    return [array copy] // Ici on renvoie une copie immuable donc à  priori y aura pas de souci.
    }

    Ici j'ai bien fait mon travail en renvoyant une copie immuable, mais la réalité ce n'est pas suffisant. Donc on doit comprendre que copy fait juste un regain....



    NSArray *paramArray = [myInstance paramArray];
    NSMutableArray *array = [paramArray firstObject];
    [array removeAllObjects];

    toute modification de array va impacter la collection de myInstance et pourtant cette dernière expose bien une collection immuable avec sa méthode paramArray. 


  • Une question plus simple : combien d'anges peuvent danser sur une tête d'épingle ?

  • AliGatorAliGator Membre, Modérateur

    Je ne partage pas cette opinion. Comprendre l'implémentation d'un mécanisme est tout aussi intéressant et important que le concept lui même.

    Je ne dis pas que ce n'est pas intéressant de savoir comment ça marche sous le capot (au contraire même, c'est bien de se poser la question). Mais je dis que dans la pratique, il ne faut surtout pas se baser sur le détail d'implémentation pour coder en conséquence. Donc ici il ne faut pas omettre le "copy" juste parce que "de toute façon connaissant l'implémentation d'Apple je sais que ça ne fait pas une vraie copie". Si ton objectif, fonctionnellement et conceptuellement parlant, c'est de faire une copie de l'objet systématiquement pour éviter d'exposer des objets internes à  l'extérieur, faut écrire [array copy], même si tu sais que array est un NSArray immutable et qu'en pratique ça ne changera rien. Car ne pas l'écrire juste parce que aujourd'hui le détail d'implémentation fait que c'est le même objet ne garantit pas que demain l'implémentation ne va pas changer, et de toute façon à  la lecture du code comme ça on voit bien quel est l'objectif / l'intention.

    Donc je ne dis pas que ce n'est pas intéressant de savoir comment ça marche sous le capot, au contraire je suis pour et trouve intéressant que les gens réfléchissent à  ce genre de problématiques et comprennent les choses plus en profondeur. Je dis juste que par contre même si c'est intéressant de savoir, ça n'empêche pas qu'il ne faut pas coder en conséquence de l'implémentation et de ce qu'on en connait, mais coder par intention.

    Pas d'accord avec toi :)
     
    Prenons ton exemple :

    ...
    return [array copy] // Ici on renvoie une copie immuable donc à  priori y aura pas de souci.
    Ici j'ai bien fait mon travail en renvoyant une copie immuable, mais la réalité ce n'est pas suffisant. Donc on doit comprendre que copy fait juste un regain....
    [code=auto:0]
    Parce que la méthode "copy" fait une Shallow Copy. C'est ce qui est expliqué dans les Programming Guides, qui expliquent justement la différence entre Shallow Copy et Deep Copy.
    Copy va te renvoyer un NSArray (et non un NSMutableArray), cela ne garantit pas pour autant que les objets que vont contenir ce NSArray immutable ne vont pas eux-même être mutable, car c'est pas une deep copy que tu fais là .

    Ca ne change en rien mes propos. Il ne faut pas se baser sur le fait que "-[NSArray copy] renvoi le NSArray inchangé parce que NSArray est immutable", donc il ne faut pas se baser sur le détail d'implémentation, ou plutôt le détail d'optimisation qui fait que "pour les classes immutable Apple ne s'est pas embêté à  faire une nouvelle allocation mémoire alors que l'objet est déjà  immutable", car c'est un détail d'optimisation. Il faut se baser sur l'intention, donc en l'occurrence sur la documentation, qui dit que "copy fait un shallow copy". Et à  l'usage, le fait qu'en pratique il ne fait pas un nouvel objet car l'objet d'origine est déjà  immutable n'y change rien, tu n'as pas besoin de connaà®tre ce détail d'implémentation pour savoir s'il faut l'utiliser.
     

    Après le fait que la méthode "copy" fait une shallow copy, c'est vrai dans tous les cas, je veux dire même si l'implémentation de "copy" sur NSArray (ou sur NSDictionary ou NSString ou ces classes immutable de base) renvoyait une nouvelle adresse mémoire plutôt que renvoyer la même adresse, ça n'y change rien.
    Par exemple pour reprendre ton exemple, il est tout aussi vrai si tu ne mets pas "copy" que tu peux quand même accéder aux sous-NSMutableArray que tu as dans ton NSArray. Même si ton objet est un NSArray ça ne l'empêche pas d'avoir dans ce NSArray des objets qui eux-même sont mutable. Le fait d'appeler "copy" ou non sur ton NSArray racine n'y change rien, par exemple si tu avais un NSMutableArray de NSMutableArray et que tu appelais copy dessus, ça te renverrai un NSArray de... NSMutableArray aussi. Car c'est une shallow copy, comme c'est précisé dans la doc.


    Tout a pour dire que dire " c'est le message que je voulais faire passer " que dire "je ne vais pas mettre 'copy' car connaissant le détail d'implémentation et l'optimisation qu'a faite Apple il se trouver que ça retourne la même adresse mémoire donc ça sert à  rien" est à  mon avis une mauvaise approche, il est préférable d'appeler la méthode car l'intention est d'avoir une (shallow) copy, même si en pratique il y a une optimisation derrière qui fait que la méthode ne change rien, plutôt que ne pas appeler la méthode parce qu'on sait qu'il y a une optimisation. Coder par intention, en utilisant les méthodes d'après ce qu'elles sont sensé faire conceptuellement, et non en se basant sur ce qu'on sait des détails d'implémentation pour essayer d'optimiser.

    Ca n'empêche pas que c'est intéressant de se poser la question et de découvrir les fonctionnements et optimisations internes pour sa propre culture, ça reste intéressant; et ça n'empêche pas qu'il faut avoir conscience que "copy" fait un shallow copy. Mais que ça n'incite pas les gens à  ne pas appeler copy sous prétexte que "parce que je connais les détails d'implémentations et d'optimisation je sais que je peux me permettre de m'en passer".



    Mais bon, Apple explique tout cela bien mieux que moi, entre ce lien sur Object Copying en général (shallow vs. deep) et ce lien encore + centré sur la copie de collections type NSArray qui convient assez largement le sujet déjà , y compris pour le dernier paragraphe de ce dernier lien qui traite de "Copying and Mutability justement.[/url]

    Une question plus simple : combien d'anges peuvent danser sur une tête d'épingle ?

    C'est exactement ce que je voulais dire par mon message en fait. A part pour la culture générale, on s'en fout dans la pratique et dans l'usage de tous ces détails. On utilise copy quand on veut une shallow copy, point barre. Pas besoin, du moins dans le but de savoir si on doit l'utiliser ou pas, de connaà®tre les détails d'implémentation, ces derniers ne sont intéressants que pour la culture mais pas pour décider de l'usage.
Connectez-vous ou Inscrivez-vous pour répondre.