Propiedad NSString: ¿copiar o retener?


331

Digamos que tengo una clase llamada SomeClasscon un stringnombre de propiedad:

@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end

Entiendo que se le puede asignar un nombre, NSMutableStringen cuyo caso esto puede conducir a un comportamiento erróneo.

  • Para las cadenas en general, ¿es siempre una buena idea usar el copyatributo en lugar de retain?
  • ¿Es una propiedad "copiada" de alguna manera menos eficiente que una propiedad "retenida"?

66
Pregunta de seguimiento: ¿se namedebe publicar dealloco no?
Chetan

77
@chetan ¡Sí, debería!
Jon

Respuestas:


440

Para los atributos cuyo tipo es una clase de valor inmutable que se ajusta al NSCopyingprotocolo, casi siempre debe especificar copyen su @propertydeclaración. Especificar retaines algo que casi nunca quieres en una situación así.

He aquí por qué quieres hacer eso:

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

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

[someName setString:@"Debajit"];

El valor actual de la Person.namepropiedad será diferente dependiendo de si la propiedad está declarada retaino copy, lo será @"Debajit"si está marcada retain, pero @"Chris"si está marcada copy.

Dado que en casi todos los casos desea evitar la mutación de los atributos de un objeto a sus espaldas, debe marcar las propiedades que los representan copy. (Y si escribe el setter usted mismo en lugar de usarlo @synthesize, recuerde usarlo en copylugar de usarlo retain).


61
Esta respuesta puede haber causado cierta confusión (ver robnapier.net/blog/implementing-nscopying-439#comment-1312 ). Estás absolutamente en lo cierto acerca de NSString, pero creo que has hecho el punto demasiado general. La razón por la que se debe copiar NSString es porque tiene una subclase mutable común (NSMutableString). Para las clases que no tienen una subclase mutable (particularmente las clases que usted escribe), generalmente es mejor retenerlas en lugar de copiar para evitar perder tiempo y memoria.
Rob Napier

63
Tu razonamiento es incorrecto. No debe hacer la determinación de si copiar o retener en función del tiempo / memoria, debe determinar eso en función de la semántica deseada. Es por eso que específicamente utilicé el término "clase de valor inmutable" en mi respuesta. Tampoco se trata de si una clase tiene subclases mutables o si es mutable en sí misma.
Chris Hanson el

10
Es una pena que Obj-C no pueda imponer la inmutabilidad por tipo. Esto es lo mismo que la falta de constante transitiva de C ++. Personalmente trabajo como si las cadenas fueran siempre inmutables. Si alguna vez necesito usar una cadena mutable, nunca entregaré una referencia inmutable si puedo mutarla más tarde. Consideraría cualquier cosa diferente como un olor a código. Como resultado, en mi código (en el que trabajo solo) utilizo retener en todas mis cadenas. Si estuviera trabajando como parte de un equipo, podría ver las cosas de manera diferente.
philsquared

55
@Phil Nash: consideraría un olor a código usar diferentes estilos para proyectos en los que trabajas solo y proyectos que compartes con otros. En cada lenguaje / marco hay reglas o estilos comunes que los desarrolladores acuerdan. Ignorarlos en proyectos privados parece incorrecto. Y por su razonamiento "En mi código, no estoy devolviendo cadenas mutables": eso podría funcionar para sus propias cadenas, pero nunca sabe sobre las cadenas que recibe de los marcos.
Nikolai Ruhe

77
@Nikolai simplemente no lo uso NSMutableString, excepto como un tipo de "generador de cadenas" transitorio (del cual tomo inmediatamente una copia inmutable). Preferiría que fueran tipos discretos, pero permitiré que el hecho de que la copia sea libre de retener si la cadena original no es mutable mitiga la mayor parte de mi preocupación.
philsquared

120

La copia debe usarse para NSString. Si es mutable, entonces se copia. Si no es así, simplemente se retiene. Exactamente la semántica que desea en una aplicación (deje que el tipo haga lo mejor).


1
Todavía preferiría que las formas mutables e inmutables fueran discretas, pero no me había dado cuenta antes de que esa copia podría conservarse si la cadena original es inmutable, que es la mayor parte del camino. Gracias.
philsquared

25
+1 por mencionar que la NSStringpropiedad declarada como copyse obtendrá de retaintodos modos (si es inmutable, por supuesto). Otro ejemplo que se me ocurre es NSNumber.
matm

¿Cuál es la diferencia entre esta respuesta y la rechazada por @GBY?
Gary Lyn

67

Para las cadenas en general, ¿es siempre una buena idea usar el atributo copy en lugar de retener?

Sí, en general siempre use el atributo de copia.

Esto se debe a que su propiedad NSString puede pasar una instancia de NSString o una instancia de NSMutableString y, por lo tanto, no podemos determinar realmente si el valor que se pasa es un objeto inmutable o mutable.

¿Es una propiedad "copiada" de alguna manera menos eficiente que una propiedad "retenida"?

  • Si su propiedad pasa una instancia de NSString , la respuesta es " No ": la copia no es menos eficiente que la retención.
    (No es menos eficiente porque NSString es lo suficientemente inteligente como para no realizar una copia).

  • Si su propiedad pasa una instancia de NSMutableString , la respuesta es " ": la copia es menos eficiente que la retención.
    (Es menos eficiente porque debe ocurrir una asignación de memoria real y una copia, pero esto es probablemente algo deseable).

  • En términos generales, una propiedad "copiada" tiene el potencial de ser menos eficiente; sin embargo, mediante el uso del NSCopyingprotocolo, es posible implementar una clase que es "tan eficiente" para copiar como para retener. Las instancias de NSString son un ejemplo de esto.

En general (no solo para NSString), ¿cuándo debo usar "copiar" en lugar de "retener"?

Siempre debe usarlo copycuando no desee que el estado interno de la propiedad cambie sin previo aviso. Incluso para objetos inmutables: los objetos inmutables escritos correctamente manejarán la copia de manera eficiente (consulte la siguiente sección sobre inmutabilidad y NSCopying).

Puede haber razones de rendimiento para los retainobjetos, pero viene con una sobrecarga de mantenimiento: debe administrar la posibilidad de que el estado interno cambie fuera de su código. Como dicen, optimice al final.

Pero, escribí que mi clase era inmutable, ¿no puedo simplemente "retenerla"?

Sin uso copy. Si su clase es realmente inmutable, entonces es una buena práctica implementar el NSCopyingprotocolo para que su clase regrese cuando copyse use. Si haces esto:

  • Otros usuarios de su clase obtendrán los beneficios de rendimiento cuando lo usen copy.
  • La copyanotación hace que su propio código sea más copyfácil de mantener; la anotación indica que realmente no necesita preocuparse por si este objeto cambia de estado en otro lugar.

39

Intento seguir esta simple regla:

  • ¿Quiero conservar el valor del objeto en el momento en que lo asigno a mi propiedad? Uso de la copia .

  • ¿Quiero aferrarme al objeto y no me importa cuáles son sus valores internos actualmente o serán en el futuro? Use fuerte (retener).

Para ilustrar: ¿Quiero aferrarme al nombre "Lisa Miller" ( copia ) o quiero aferrarme a la persona Lisa Miller ( fuerte )? Su nombre podría cambiar luego a "Lisa Smith", pero seguirá siendo la misma persona.


14

A través de este ejemplo, copiar y retener se puede explicar como:

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

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

[someName setString:@"Debajit"];

si la propiedad es de tipo copia entonces,

Se creará una nueva copia para la [Person name]cadena que contendrá el contenido de la someNamecadena. Ahora cualquier operación en someNamecadena no tendrá efecto [Person name].

[Person name]y las someNamecadenas tendrán diferentes direcciones de memoria.

Pero en caso de retener,

ambos [Person name]contendrán la misma dirección de memoria que la cadena de nombre, solo el recuento retenido de la cadena de nombre se incrementará en 1.

Por lo tanto, cualquier cambio en la cadena de nombre se reflejará en la [Person name]cadena.


3

Seguramente, poner 'copia' en una declaración de propiedad se opone al uso de un entorno orientado a objetos donde los objetos en el montón se pasan por referencia: uno de los beneficios que obtiene aquí es que, al cambiar un objeto, todas las referencias a ese objeto ver los últimos cambios. Muchos idiomas proporcionan 'ref' o palabras clave similares para permitir que los tipos de valor (es decir, estructuras en la pila) se beneficien del mismo comportamiento. Personalmente, usaría la copia con moderación, y si considerara que el valor de una propiedad debe protegerse de los cambios realizados en el objeto desde el que fue asignado, podría llamar al método de copia de ese objeto durante la asignación, por ejemplo:

p.name = [someName copy];

Por supuesto, al diseñar el objeto que contiene esa propiedad, solo usted sabrá si el diseño se beneficia de un patrón donde las tareas toman copias: Cocoawithlove.com tiene lo siguiente que decir:

"Debe usar un descriptor de acceso de copia cuando el parámetro setter puede ser mutable pero no puede cambiar el estado interno de una propiedad sin previo aviso ", por lo que la decisión de si puede soportar el valor de cambiar inesperadamente es suya. Imagina este escenario:

//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.

En este caso, sin usar la copia, nuestro objeto de contacto toma el nuevo valor automáticamente; si lo usáramos, sin embargo, tendríamos que asegurarnos manualmente de que los cambios fueron detectados y sincronizados. En este caso, retener la semántica puede ser deseable; en otro, la copia podría ser más apropiada.


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

0

Debe usar la copia todo el tiempo para declarar la propiedad NSString

@property (nonatomic, copy) NSString* name;

Debe leerlos para obtener más información sobre si devuelve una cadena inmutable (en caso de que se haya pasado una cadena mutable) o si devuelve una cadena retenida (en caso de que se haya pasado una cadena inmutable)

Referencia de protocolo de copia NS

Implemente NSCopying conservando el original en lugar de crear una nueva copia cuando la clase y su contenido sean inmutables

Objetos de valor

Entonces, para nuestra versión inmutable, podemos hacer esto:

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

-1

Como el nombre es un (inmutable) NSString, copiar o retener no hace ninguna diferencia si configura otro NSStringnombre. En otras palabras, copiar se comporta como retener, aumentando el recuento de referencias en uno. Creo que es una optimización automática para clases inmutables, ya que son inmutables y no necesitan ser clonadas. Pero cuando NSMutalbeString mstrse establece un nombre, el contenido de mstrse copiará en aras de la corrección.


1
Está confundiendo el tipo declarado con el tipo real. Si utiliza una propiedad "retener" y asigna una NSMutableString, esa NSMutableString se mantendrá, pero aún puede modificarse. Si usa "copy", se creará una copia inmutable cuando asigne una NSMutableString; a partir de entonces, la "copia" en la propiedad solo se mantendrá, porque la copia de la cadena mutable es inmutable.
gnasher729

1
Le faltan algunos hechos importantes aquí, si usa un objeto que proviene de una variable retenida, cuando esa variable se modifica, también lo hace su objeto, si vino de una variable copiada, su objeto tendrá el valor actual de la variable que no cambiará
Bryan P

-1

Si la cadena es muy grande, la copia afectará el rendimiento y dos copias de la cadena grande usarán más memoria.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.