Comment copier un objet dans Objective-C

112

J'ai besoin de copier en profondeur un objet personnalisé qui a ses propres objets. J'ai lu et je ne sais pas trop comment hériter de NSCopying et comment utiliser NSCopyObject.

ben
la source
1
Le grand tutoriel pour comprendre la copie, mutableCopy et copyWithZone
horkavlna

Réponses:

192

Comme toujours avec les types référence, il existe deux notions de «copie». Je suis sûr que vous les connaissez, mais par souci d'exhaustivité.

  1. Une copie au niveau du bit. En cela, nous copions simplement la mémoire bit pour bit - c'est ce que fait NSCopyObject. Presque toujours, ce n'est pas ce que vous voulez. Les objets ont un état interne, d'autres objets, etc., et supposent souvent qu'ils sont les seuls à contenir des références à ces données. Les copies au niveau du bit cassent cette hypothèse.
  2. Une copie profonde et logique. En cela, nous faisons une copie de l'objet, mais sans le faire petit à petit - nous voulons un objet qui se comporte de la même manière à toutes fins utiles, mais n'est pas (nécessairement) un clone identique à la mémoire de l'original - le manuel Objective C appelle un tel objet "fonctionnellement indépendant" de son original. Comme les mécanismes de réalisation de ces copies «intelligentes» varient d'une classe à l'autre, nous demandons aux objets eux-mêmes de les exécuter. C'est le protocole NSCopying.

Vous voulez ce dernier. S'il s'agit de l'un de vos propres objets, vous devez simplement adopter le protocole NSCopying et implémenter - (id) copyWithZone: (NSZone *) zone. Vous êtes libre de faire ce que vous voulez; bien que l'idée soit de faire une vraie copie de vous-même et de la renvoyer. Vous appelez copyWithZone sur tous vos champs, pour faire une copie profonde. Un exemple simple est

@interface YourClass : NSObject <NSCopying> 
{
   SomeOtherObject *obj;
}

// In the implementation
-(id)copyWithZone:(NSZone *)zone
{
  // We'll ignore the zone for now
  YourClass *another = [[YourClass alloc] init];
  another.obj = [obj copyWithZone: zone];

  return another;
}
Adam Wright
la source
Mais vous rendez le destinataire de l'objet copié responsable de le libérer! Ne devriez-vous autoreleasepas, ou est-ce que je manque quelque chose ici
bobobobo
30
@bobobobo: Non, la règle fondamentale de la gestion de la mémoire Objective-C est: Vous prenez possession d'un objet si vous le créez à l'aide d'une méthode dont le nom commence par «alloc» ou «new» ou contient «copy». copyWithZone:répond à ces critères, il doit donc renvoyer un objet avec un nombre de rétention de +1.
Steve Madsen
1
@Adam Y a-t-il une raison d'utiliser allocau lieu de allocWithZone:puisque la zone a été transmise?
Richard
3
Eh bien, les zones sont effectivement inutilisées dans les environnements d'exécution basés sur OS X modernes (c'est-à-dire je pense qu'elles ne sont littéralement jamais utilisées). Mais oui, vous pouvez appeler allocWithZone.
Adam Wright
25

La documentation Apple dit

Une version de sous-classe de la méthode copyWithZone: doit envoyer le message à super en premier, pour incorporer son implémentation, à moins que la sous-classe ne descende directement de NSObject.

pour ajouter à la réponse existante

@interface YourClass : NSObject <NSCopying> 
{
   SomeOtherObject *obj;
}

// In the implementation
-(id)copyWithZone:(NSZone *)zone
{
  YourClass *another = [super copyWithZone:zone];
  another.obj = [obj copyWithZone: zone];

  return another;
}
Saqib Saud
la source
2
Puisque YourClass descend directement de NSObject, je ne pense pas que ce soit nécessaire ici
Mike
2
Bon point, mais c'est une règle générale, au cas où c'est une longue hiérarchie de classes.
Saqib Saud
8
Je suis une erreur: No visible @interface for 'NSObject' declares the selector 'copyWithZone:'. Je suppose que cela n'est nécessaire que lorsque nous héritons d'une autre classe personnalisée qui implémentecopyWithZone
Sam
1
another.obj = [[obj copyWithZone: zone] autorelease]; pour toutes les sous-classes de NSObject. Et pour les types de données primitifs, attribuez-les simplement -> another.someBOOL = self.someBOOL;
hariszaman
@Sam "NSObject ne prend pas lui-même en charge le protocole NSCopying. Les sous-classes doivent prendre en charge le protocole et implémenter la méthode copyWithZone:. Une version de sous-classe de la méthode copyWithZone: doit envoyer le message à super d'abord, pour incorporer son implémentation, sauf si la sous-classe descend directement de NSObject. " developer.apple.com/documentation/objectivec/nsobject/…
s4mt6
21

Je ne connais pas la différence entre ce code et le mien, mais j'ai des problèmes avec cette solution, alors j'ai lu un peu plus et j'ai trouvé que nous devions définir l'objet avant de le renvoyer. Je veux dire quelque chose comme:

#import <Foundation/Foundation.h>

@interface YourObject : NSObject <NSCopying>

@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *line;
@property (strong, nonatomic) NSMutableString *tags;
@property (strong, nonatomic) NSString *htmlSource;
@property (strong, nonatomic) NSMutableString *obj;

-(id) copyWithZone: (NSZone *) zone;

@end


@implementation YourObject


-(id) copyWithZone: (NSZone *) zone
{
    YourObject *copy = [[YourObject allocWithZone: zone] init];

    [copy setNombre: self.name];
    [copy setLinea: self.line];
    [copy setTags: self.tags];
    [copy setHtmlSource: self.htmlSource];

    return copy;
}

J'ai ajouté cette réponse parce que j'ai beaucoup de problèmes avec ce problème et je n'ai aucune idée de pourquoi cela se produit. Je ne connais pas la différence, mais ça marche pour moi et peut-être que ça peut aussi être utile pour les autres :)

Felipe Quirós
la source
3
another.obj = [obj copyWithZone: zone];

Je pense que cette ligne provoque une fuite de mémoire, car vous accédez à la objpropriété through qui est (je suppose) déclarée comme retain. Ainsi, le nombre de rétention sera augmenté par la propriété et copyWithZone.

Je pense que cela devrait être:

another.obj = [[obj copyWithZone: zone] autorelease];

ou:

SomeOtherObject *temp = [obj copyWithZone: zone];
another.obj = temp;
[temp release]; 
Szuwar_Jr
la source
Non, les méthodes alloc, copy, mutableCopy, new devraient renvoyer des objets non libérés automatiquement.
kovpas
@kovpas, êtes-vous sûr que vous me comprenez bien? Je ne parle pas d'objet retourné, je parle de ses champs de données.
Szuwar_Jr
ouais, mon mauvais, désolé. pourriez-vous s'il vous plaît modifier votre réponse d'une manière ou d'une autre afin que je puisse supprimer moins? :))
kovpas
0

Il y a aussi l'utilisation de l'opérateur -> pour la copie. Par exemple:

-(id)copyWithZone:(NSZone*)zone
{
    MYClass* copy = [MYClass new];
    copy->_property1 = self->_property1;
    ...
    copy->_propertyN = self->_propertyN;
    return copy;
}

Le raisonnement ici est que l'objet copié résultant doit refléter l'état de l'objet d'origine. Le "." l'opérateur pourrait introduire des effets secondaires car celui-ci appelle des getters qui à leur tour peuvent contenir de la logique.

Alex Nolasco
la source