Cómo copiar un objeto en Objective-C

112

Necesito realizar una copia profunda de un objeto personalizado que tenga sus propios objetos. He estado leyendo y estoy un poco confundido sobre cómo heredar NSCopying y cómo usar NSCopyObject.

ben
fuente
1
El gran TUTORIAL para entender copy, mutableCopy y copyWithZone
horkavlna

Respuestas:

192

Como siempre ocurre con los tipos de referencia, hay dos nociones de "copia". Estoy seguro de que los conoce, pero para completarlos.

  1. Una copia bit a bit. En esto, simplemente copiamos la memoria bit por bit; esto es lo que hace NSCopyObject. Casi siempre, no es lo que quieres. Los objetos tienen un estado interno, otros objetos, etc., y a menudo suponen que son los únicos que tienen referencias a esos datos. Las copias bit a bit rompen esta suposición.
  2. Una copia lógica y profunda. En esto, hacemos una copia del objeto, pero sin hacerlo poco a poco; queremos un objeto que se comporte de la misma manera para todos los efectos, pero que no sea (necesariamente) un clon idéntico a la memoria del original. el manual de Objective C llama a dicho objeto "funcionalmente independiente" de su original. Debido a que los mecanismos para hacer estas copias "inteligentes" varían de una clase a otra, pedimos a los propios objetos que las realicen. Este es el protocolo NSCopying.

Quieres lo último. Si este es uno de sus propios objetos, simplemente debe adoptar el protocolo NSCopying e implementar - (id) copyWithZone: (NSZone *) zone. Eres libre de hacer lo que quieras; aunque la idea es que hagas una copia real de ti mismo y la devuelvas. Llame a copyWithZone en todos sus campos para hacer una copia en profundidad. Un ejemplo simple es

@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
fuente
¡Pero estás haciendo responsable al destinatario del objeto copiado de liberarlo! No deberiasautorelease , o me estoy perdiendo algo aquí?
bobobobo
30
@bobobobo: No, la regla fundamental de la administración de memoria de Objective-C es: Usted toma posesión de un objeto si lo crea usando un método cuyo nombre comienza con "alloc" o "nuevo" o contiene "copia". copyWithZone:cumple con este criterio, por lo tanto, debe devolver un objeto con un recuento de retención de +1.
Steve Madsen
1
@Adam ¿Hay alguna razón para usar en alloclugar de allocWithZone:desde que se pasó la zona?
Richard
3
Bueno, las zonas no se usan de manera efectiva en los tiempos de ejecución modernos basados ​​en OS X (es decir, creo que literalmente nunca se usan). Pero sí, podrías llamar allocWithZone.
Adam Wright
25

La documentación de Apple dice

Una versión de subclase del método copyWithZone: debe enviar el mensaje a super primero, para incorporar su implementación, a menos que la subclase descienda directamente de NSObject.

para agregar a la respuesta existente

@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
fuente
2
Dado que YourClass desciende directamente de NSObject, no creo que sea necesario aquí
Mike
2
Buen punto, pero es una regla general, en caso de que sea una jerarquía de clases larga.
Saqib Saud
8
Tengo un error: No visible @interface for 'NSObject' declares the selector 'copyWithZone:'. Supongo que esto solo es necesario cuando heredamos de alguna otra clase personalizada que implementacopyWithZone
Sam
1
otro.obj = [[obj copyWithZone: zone] autorelease]; para todas las subclases de NSObject. Y para los tipos de datos primitivos, simplemente asignelos -> another.someBOOL = self.someBOOL;
hariszaman
@Sam "NSObject no admite en sí mismo el protocolo NSCopying. Las subclases deben admitir el protocolo e implementar el método copyWithZone:. Una versión de subclase del método copyWithZone: debe enviar el mensaje a super first, para incorporar su implementación, a menos que la subclase descienda directamente de NSObject ". developer.apple.com/documentation/objectivec/nsobject/…
s4mt6
21

No sé la diferencia entre ese código y el mío, pero tengo problemas con esa solución, así que leí un poco más y descubrí que tenemos que configurar el objeto antes de devolverlo. Me refiero a algo como:

#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;
}

Agregué esta respuesta porque tengo muchos problemas con este problema y no tengo ni idea de por qué está sucediendo. No sé la diferencia, pero está funcionando para mí y tal vez también pueda ser útil para otros :)

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

Creo que esta línea causa una pérdida de memoria, porque accede a la objpropiedad a través de la cual (supongo) se declara como retain. Entonces, el conteo de retención aumentará por propiedad y copyWithZone.

Creo que debería ser:

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

o:

SomeOtherObject *temp = [obj copyWithZone: zone];
another.obj = temp;
[temp release]; 
Szuwar_Jr
fuente
No, los métodos alloc, copy, mutableCopy, new deberían devolver objetos no liberados automáticamente.
kovpas
@kovpas, ¿estás seguro de que me entiendes verdad? No estoy hablando de un objeto devuelto, estoy hablando de sus campos de datos.
Szuwar_Jr
sí, mi mal, lo siento. ¿Podría editar su respuesta de alguna manera para que pueda eliminar menos? :))
kovpas
0

También existe el uso del operador -> para copiar. Por ejemplo:

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

El razonamiento aquí es que el objeto copiado resultante debe reflejar el estado del objeto original. Los "." El operador podría introducir efectos secundarios, ya que este llama a los captadores que, a su vez, pueden contener lógica.

Alex Nolasco
fuente