Propriedade NSString: copiar ou reter?

331

Digamos que eu tenha uma classe chamada SomeClasscom um stringnome de propriedade:

@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end

Entendo que esse nome pode ser atribuído a NSMutableString, caso em que isso pode levar a um comportamento incorreto.

  • Para cadeias de caracteres em geral, é sempre uma boa ideia usar o copyatributo em vez de retain?
  • Uma propriedade "copiada" é menos eficiente do que uma propriedade "retida"?
PlagueHammer
fonte
6
Pergunta de acompanhamento: deve nameser lançado deallocou não?
Chetan 03/03
7
@chetan Sim, deveria!
22411 Jon

Respostas:

440

Para atributos cujo tipo é uma classe de valor imutável que está em conformidade com o NSCopyingprotocolo, você quase sempre deve especificar copyem sua @propertydeclaração. Especificar retainé algo que você quase nunca deseja em tal situação.

Aqui está o porquê você deseja fazer isso:

NSMutableString *someName = [NSMutableString stringWithString:@"Chris"];

Person *p = [[[Person alloc] init] autorelease];
p.name = someName;

[someName setString:@"Debajit"];

O valor atual da Person.namepropriedade será diferente dependendo se a propriedade for declarada retainou copy- será @"Debajit"se a propriedade estiver marcada retain, mas @"Chris"se a propriedade estiver marcada copy.

Como em quase todos os casos você deseja impedir a mutação dos atributos de um objeto pelas costas, marque as propriedades que os representam copy. (E se você mesmo escrever o setter em vez de usá- @synthesizelo, lembre-se de usá- lo em copyvez de usá- retainlo.)

Chris Hanson
fonte
61
Esta resposta pode ter causado alguma confusão (consulte robnapier.net/blog/implementing-nscopying-439#comment-1312 ). Você está absolutamente correto sobre o NSString, mas acredito que você fez a observação um pouco genérica demais. O motivo pelo qual o NSString deve ser copiado é que ele possui uma subclasse mutável comum (NSMutableString). Para classes que não possuem uma subclasse mutável (principalmente as que você escreve), geralmente é melhor retê-las do que copiar para evitar desperdiçar tempo e memória.
Rob Napier
63
Seu raciocínio está incorreto. Você não deve decidir se deseja copiar ou reter com base no tempo / memória, mas sim na semântica desejada. Por isso, usei especificamente o termo "classe de valor imutável" em minha resposta. Também não é uma questão de saber se uma classe tem subclasses mutáveis ​​ou é ela própria mutável.
Chris Hanson
10
É uma pena que o Obj-C não possa impor imutabilidade por tipo. É o mesmo que a falta de const transitiva do C ++. Pessoalmente, trabalho como se as cordas fossem sempre imutáveis. Se eu precisar usar uma sequência mutável, nunca entregarei uma referência imutável, se puder modificá-la posteriormente. Eu consideraria algo diferente como um cheiro de código. Como resultado - no meu código (em que trabalho sozinho), uso reter todas as minhas strings. Se eu estivesse trabalhando como parte de uma equipe, poderia ver as coisas de maneira diferente.
Philsquared
5
@ Phil Nash: Eu consideraria um cheiro de código usar estilos diferentes para projetos nos quais você trabalha sozinho e projetos que compartilha com outras pessoas. Em todos os idiomas / estruturas, existem regras ou estilos comuns com os quais os desenvolvedores concordam. Desprezá-los em projetos privados parece errado. E por sua lógica "No meu código, não estou retornando seqüências mutáveis": Isso pode funcionar para suas próprias sequências, mas você nunca sabe sobre as sequências que recebe das estruturas.
Nikolai Ruhe
7
@ Nikolai Eu simplesmente não uso NSMutableString, exceto como um tipo de "construtor de string" transitório (do qual eu imediatamente tiro uma cópia imutável). Eu preferiria que fossem tipos discretos - mas permitirei que o fato de que a cópia seja livre para reter se a sequência original não for mutável atenua a maior parte da minha preocupação.
Philsquared
120

A cópia deve ser usada para o NSString. Se for Mutável, será copiado. Se não estiver, apenas será retido. Exatamente a semântica que você deseja em um aplicativo (deixe o tipo fazer o que é melhor).

Frank Krueger
fonte
1
Eu ainda preferiria que as formas mutáveis ​​e imutáveis ​​fossem discretas, mas não havia percebido antes que essa cópia possa ser mantida apenas se a sequência original for imutável - o que ocorre na maior parte do caminho. Obrigado.
philsquared
25
+1 por mencionar que a NSStringpropriedade declarada como copyreceberá de retainqualquer maneira (se for imutável, é claro). Outro exemplo em que posso pensar é NSNumber.
matm
Qual é a diferença entre esta resposta e a votada para baixo por @GBY?
precisa
67

Para strings em geral, é sempre uma boa ideia usar o atributo copy em vez de reter?

Sim - em geral, sempre use o atributo copy.

Isso ocorre porque sua propriedade NSString pode receber uma instância NSString ou uma instância NSMutableString e, portanto, não podemos realmente determinar se o valor que está sendo passado é um objeto imutável ou mutável.

Uma propriedade "copiada" é menos eficiente do que uma propriedade "retida"?

  • Se sua propriedade está sendo transmitida para uma instância NSString , a resposta é " Não " - a cópia não é menos eficiente do que reter.
    (Não é menos eficiente, porque o NSString é inteligente o suficiente para não executar uma cópia.)

  • Se sua propriedade receber uma instância de NSMutableString , a resposta será " Sim " - a cópia é menos eficiente que a retenção.
    (É menos eficiente porque uma alocação e cópia de memória real deve ocorrer, mas isso é provavelmente uma coisa desejável.)

  • De um modo geral, uma propriedade "copiada" tem o potencial de ser menos eficiente - no entanto, com o uso do NSCopyingprotocolo, é possível implementar uma classe que é "tão eficiente" para copiar quanto para reter. As instâncias NSString são um exemplo disso.

Geralmente (não apenas para NSString), quando devo usar "copiar" em vez de "reter"?

Você sempre deve usar copyquando não deseja que o estado interno da propriedade seja alterado sem aviso. Mesmo para objetos imutáveis ​​- objetos imutáveis ​​escritos corretamente manipularão a cópia com eficiência (consulte a próxima seção sobre imutabilidade e NSCopying).

Pode haver motivos de desempenho para os retainobjetos, mas ele vem com uma sobrecarga de manutenção - você deve gerenciar a possibilidade de o estado interno mudar fora do seu código. Como se costuma dizer - otimizar por último.

Mas escrevi minha classe para ser imutável - não posso simplesmente "retê-la"?

Não - use copy. Se sua classe é realmente imutável, é uma boa prática implementar o NSCopyingprotocolo para fazer com que ela retorne quando copyfor usada. Se você fizer isto:

  • Outros usuários da sua classe obterão os benefícios de desempenho quando usarem copy.
  • A copyanotação torna seu próprio código mais sustentável - a copyanotação indica que você realmente não precisa se preocupar com esse objeto alterando o estado em outro lugar.
TJez
fonte
39

Eu tento seguir esta regra simples:

  • Desejo manter o valor do objeto no momento em que o atribui à minha propriedade? Use cópia .

  • Eu quero me apegar ao objeto e não me importo com quais são seus valores internos atualmente ou serão no futuro? Use forte (reter).

Para ilustrar: Eu quero me apegar ao nome "Lisa Miller" ( cópia ) ou quero me apegar à pessoa Lisa Miller ( forte )? Seu nome pode mais tarde mudar para "Lisa Smith", mas ela ainda será a mesma pessoa.

Johannes Fahrenkrug
fonte
14

Através deste exemplo, copiar e reter pode ser explicado como:

NSMutableString *someName = [NSMutableString stringWithString:@"Chris"];

Person *p = [[[Person alloc] init] autorelease];
p.name = someName;

[someName setString:@"Debajit"];

se a propriedade for do tipo copy,

uma nova cópia será criada para a [Person name]sequência que conterá o conteúdo da someNamesequência. Agora qualquer operação na someNamestring não terá efeito [Person name].

[Person name]e someNamestrings terão diferentes endereços de memória.

Mas em caso de retenção,

ambos [Person name]manterão o mesmo endereço de memória da sequência de nomes de som, apenas a contagem de retenção da sequência de nomes de nomes será incrementada em 1.

Portanto, qualquer alteração na cadeia de caracteres de um nome será refletida na [Person name]cadeia de caracteres.

Divya Arora
fonte
3

Certamente, colocar 'copiar' em uma declaração de propriedade não deve ser usado em um ambiente orientado a objetos, onde objetos na pilha são passados ​​por referência - um dos benefícios que você obtém aqui é que, ao alterar um objeto, todas as referências a esse objeto veja as últimas alterações. Muitos idiomas fornecem 'ref' ou palavras-chave semelhantes para permitir que tipos de valor (ou seja, estruturas na pilha) se beneficiem do mesmo comportamento. Pessoalmente, eu usaria a cópia com moderação e, se considerasse que um valor de propriedade deveria ser protegido de alterações feitas no objeto do qual foi atribuído, poderia chamar o método de cópia desse objeto durante a atribuição, por exemplo:

p.name = [someName copy];

Obviamente, ao projetar o objeto que contém essa propriedade, apenas você saberá se o design se beneficia de um padrão em que as atribuições tiram cópias - Cocoawithlove.com tem o seguinte a dizer:

"Você deve usar um acessador de cópia quando o parâmetro setter puder ser mutável, mas não for possível alterar o estado interno de uma propriedade sem aviso " ", portanto, o julgamento sobre se você pode suportar o valor de alterar inesperadamente é seu. Imagine este cenário:

//person object has details of an individual you're assigning to a contact list.

Contact *contact = [[[Contact alloc] init] autorelease];
contact.name = person.name;

//person changes name
[[person name] setString:@"new name"];
//now both person.name and contact.name are in sync.

Nesse caso, sem usar a cópia, nosso objeto de contato recebe o novo valor automaticamente; se o utilizássemos, teríamos de garantir manualmente que as alterações fossem detectadas e sincronizadas. Nesse caso, reter semântica pode ser desejável; em outro, a cópia pode ser mais apropriada.

Clarkeye
fonte
1
@interface TTItem : NSObject    
@property (nonatomic, copy) NSString *name;
@end

{
    TTItem *item = [[TTItem alloc] init];    
    NSString *test1 = [NSString stringWithFormat:@"%d / %@", 1, @"Go go go"];  
    item.name = test1;  
    NSLog(@"-item.name: point = %p, content = %@; test1 = %p", item.name, item.name, test1);  
    test1 = [NSString stringWithFormat:@"%d / %@", 2, @"Back back back"];  
    NSLog(@"+item.name: point = %p, content = %@, test1 = %p", item.name, item.name, test1);
}

Log:  
    -item.name: point = 0x9a805a0, content = 1 / Go go go; test1 = 0x9a805a0  
    +item.name: point = 0x9a805a0, content = 1 / Go go go, test1 = 0x9a84660
len
fonte
0

Você deve usar copiar o tempo todo para declarar a propriedade NSString

@property (nonatomic, copy) NSString* name;

Você deve lê-los para obter mais informações sobre se ela retorna uma sequência imutável (no caso de uma sequência mutável ter sido passada) ou retorna uma sequência de caracteres retida (no caso de uma sequência imutável)

Referência do Protocolo NSCopying

Implemente NSCopying mantendo o original em vez de criar uma nova cópia quando a classe e seu conteúdo forem imutáveis

Objetos de valor

Portanto, para a nossa versão imutável, podemos fazer o seguinte:

- (id)copyWithZone:(NSZone *)zone
{
    return self;
}
onmyway133
fonte
-1

Como o nome é (imutável) NSString, copiar ou reter não faz diferença se você definir outro NSStringcomo nome. Em outra palavra, a cópia se comporta exatamente como reter, aumentando a contagem de referência em um. Eu acho que é uma otimização automática para classes imutáveis, uma vez que são imutáveis ​​e não precisam ser clonadas. Mas quando a NSMutalbeString mstré definido como o nome, o conteúdo de mstrserá copiado por uma questão de correção.

GBY
fonte
1
Você está confundindo o tipo declarado com o tipo real. Se você usar uma propriedade "reter" e atribuir um NSMutableString, esse NSMutableString será retido, mas ainda poderá ser modificado. Se você usar "copy", uma cópia imutável será criada quando você atribuir um NSMutableString; a partir de então, "copiar" na propriedade será retida, porque a cópia da sequência mutável é imutável.
precisa saber é o seguinte
1
Você está perdendo alguns fatos importantes aqui, se você usar um objeto que veio de uma variável retida, quando essa variável for modificada, o mesmo ocorre com o seu objeto, se vier de uma variável copiada, seu objeto terá o valor atual da variável que não vai mudar
Bryan P
-1

Se a sequência for muito grande, a cópia afetará o desempenho e duas cópias da sequência grande usarão mais memória.

jack
fonte