Como copiar um objeto em Objective-C

112

Preciso copiar em profundidade um objeto personalizado que possui objetos próprios. Tenho lido por aí e estou um pouco confuso sobre como herdar NSCopying e como usar NSCopyObject.

ben
fonte
1
O grande TUTORIAL para entender copy, mutableCopy e copyWithZone
horkavlna

Respostas:

192

Como sempre com os tipos de referência, existem duas noções de "cópia". Tenho certeza que você os conhece, mas para a integridade.

  1. Uma cópia bit a bit. Nesse caso, apenas copiamos o bit de memória por bit - isso é o que NSCopyObject faz. Quase sempre, não é o que você deseja. Os objetos têm estado interno, outros objetos, etc, e geralmente fazem suposições de que são os únicos que mantêm referências a esses dados. As cópias bit a bit quebram essa suposição.
  2. Uma cópia profunda e lógica. Nesse caso, fazemos uma cópia do objeto, mas sem realmente fazê-lo aos poucos - queremos um objeto que se comporte da mesma forma para todos os efeitos, mas não seja (necessariamente) um clone idêntico à memória do original - o manual do Objective C chama esse objeto de "funcionalmente independente" de seu original. Como os mecanismos para fazer essas cópias "inteligentes" variam de classe para classe, pedimos aos próprios objetos que as realizem. Este é o protocolo NSCopy.

Você quer o último. Se este for um de seus próprios objetos, você precisa simplesmente adotar o protocolo NSCopying e implementar - (id) copyWithZone: (NSZone *) zone. Você é livre para fazer o que quiser; embora a ideia seja fazer uma cópia real de si mesmo e devolvê-la. Você chama copyWithZone em todos os seus campos para fazer uma cópia em profundidade. Um exemplo simples é

@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
fonte
Mas você está tornando o destinatário do objeto copiado responsável por liberá-lo! Não deveria autorelease, ou estou perdendo alguma coisa aqui?
bobobobo
30
@bobobobo: Não, a regra fundamental do gerenciamento de memória Objective-C é: Você assume a propriedade de um objeto se o cria usando um método cujo nome começa com “aloc” ou “novo” ou contém “cópia”. copyWithZone:atende a esse critério, portanto, deve retornar um objeto com uma contagem de retenção de +1.
Steve Madsen
1
@Adam Existe uma razão para usar em allocvez de, allocWithZone:já que a zona foi aprovada?
Richard
3
Bem, as zonas não são usadas efetivamente em tempos de execução modernos baseados no OS X (ou seja, acho que literalmente nunca são usadas). Mas sim, você pode ligar allocWithZone.
Adam Wright
25

A documentação da Apple diz

Uma versão de subclasse do método copyWithZone: deve enviar a mensagem para super primeiro, para incorporar sua implementação, a menos que a subclasse desça diretamente de NSObject.

para adicionar à resposta 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
fonte
2
Como YourClass desce diretamente de NSObject, não acho que seja necessário aqui
Mike
2
Bom ponto, mas é uma regra geral, no caso de ser uma longa hierarquia de classes.
Saqib Saud
8
Eu tenho um erro: No visible @interface for 'NSObject' declares the selector 'copyWithZone:'. Acho que isso só é necessário quando estamos herdando de alguma outra classe personalizada que implementacopyWithZone
Sam
1
another.obj = [[obj copyWithZone: zone] autorelease]; para todas as subclasses de NSObject. E para tipos de dados primitivos, basta atribuí-los -> another.someBOOL = self.someBOOL;
hariszaman
@Sam "NSObject não suporta o protocolo NSCopying. As subclasses devem suportar o protocolo e implementar o método copyWithZone:. Uma versão de subclasse do método copyWithZone: deve enviar a mensagem para o super primeiro, para incorporar sua implementação, a menos que a subclasse desça diretamente de NSObject. " developer.apple.com/documentation/objectivec/nsobject/…
s4mt6
21

Não sei a diferença entre esse código e o meu, mas tenho problemas com essa solução, então li um pouco mais e descobri que temos que definir o objeto antes de devolvê-lo. Quero dizer 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;
}

Eu adicionei essa resposta porque tenho muitos problemas com esse problema e não tenho ideia do por que isso está acontecendo. Não sei a diferença, mas está funcionando para mim e talvez possa ser útil para os outros também:)

Felipe quirós
fonte
3
another.obj = [obj copyWithZone: zone];

Eu acho que essa linha causa vazamento de memória, porque você acessa objatravés da propriedade que é (presumo) declarada como retain. Portanto, a contagem retida será aumentada por propriedade e copyWithZone.

Eu acredito que deveria ser:

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

ou:

SomeOtherObject *temp = [obj copyWithZone: zone];
another.obj = temp;
[temp release]; 
Szuwar_Jr
fonte
Não, métodos alloc, copy, mutableCopy, new devem retornar objetos não autoreleased.
kovpas de
@kovpas, tem certeza de que me entendeu bem? Não estou falando sobre o objeto retornado, estou falando sobre seus campos de dados.
Szuwar_Jr
sim, meu mal, desculpe. você poderia editar sua resposta de alguma forma para que eu pudesse remover o sinal de menos? :))
kovpas de
0

Também existe o uso do operador -> para copiar. Por exemplo:

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

O raciocínio aqui é que o objeto copiado resultante deve refletir o estado do objeto original. O "." operador pode introduzir efeitos colaterais, pois este chama getters que, por sua vez, podem conter lógica.

Alex Nolasco
fonte