Propriété NSString: copier ou conserver?

331

Disons que j'ai une classe appelée SomeClassavec un stringnom de propriété:

@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end

Je comprends que le nom peut être attribué un NSMutableStringauquel cas cela peut conduire à un comportement errant.

  • Pour les chaînes en général, est-ce toujours une bonne idée d'utiliser l' copyattribut au lieu de retain?
  • Une propriété "copiée" est-elle d'une quelconque manière moins efficace qu'une telle propriété "conservée"?
PlagueHammer
la source
6
Question de suivi: devrait nameêtre libéré deallocou non?
Chetan
7
@chetan Oui, ça devrait!
Jon

Réponses:

440

Pour les attributs dont le type est une classe de valeur immuable conforme au NSCopyingprotocole, vous devez presque toujours le spécifier copydans votre @propertydéclaration. Spécifier retainest quelque chose que vous ne voulez presque jamais dans une telle situation.

Voici pourquoi vous voulez faire ça:

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

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

[someName setString:@"Debajit"];

La valeur actuelle de la Person.namepropriété sera différente selon que la propriété est déclarée retainou copy- ce sera @"Debajit"si la propriété est marquée retain, mais @"Chris"si la propriété est marquée copy.

Étant donné que, dans presque tous les cas, vous souhaitez empêcher la mutation des attributs d'un objet derrière son dos, vous devez marquer les propriétés qui les représentent copy. (Et si vous écrivez le setter vous-même au lieu de l'utiliser, @synthesizevous devez vous souvenir de l'utiliser réellement copyau lieu de retaindedans.)

Chris Hanson
la source
61
Cette réponse peut avoir causé une certaine confusion (voir robnapier.net/blog/implementing-nscopying-439#comment-1312 ). Vous avez tout à fait raison au sujet de NSString, mais je pense que vous avez fait un point un peu trop général. La raison pour laquelle NSString doit être copié est qu'il a une sous-classe mutable commune (NSMutableString). Pour les classes qui n'ont pas de sous-classe modifiable (en particulier les classes que vous écrivez vous-même), il est généralement préférable de les conserver plutôt que de les copier afin d'éviter de perdre du temps et de la mémoire.
Rob Napier
63
Votre raisonnement est incorrect. Vous ne devez pas déterminer si copier ou conserver en fonction du temps / de la mémoire, vous devez déterminer cela en fonction de la sémantique souhaitée. C'est pourquoi j'ai spécifiquement utilisé le terme «classe de valeur immuable» dans ma réponse. Il ne s'agit pas non plus de savoir si une classe a des sous-classes mutables ou si elle est elle-même mutable.
Chris Hanson
10
C'est dommage qu'Obj-C ne puisse pas imposer l'immuabilité par type. C'est la même chose que le manque de const transitive de C ++. Personnellement, je travaille comme si les cordes étaient toujours immuables. Si jamais j'ai besoin d'utiliser une chaîne mutable, je ne distribuerai jamais une référence immuable si je peux la muter plus tard. Je considérerais que quelque chose de différent est une odeur de code. En conséquence - dans mon code (sur lequel je travaille seul), j'utilise la retenue sur toutes mes chaînes. Si je travaillais en équipe, je pourrais voir les choses différemment.
philsquared
5
@Phil Nash: Je considérerais comme une odeur de code d'utiliser différents styles pour les projets sur lesquels vous travaillez seul et les projets que vous partagez avec d'autres. Dans chaque langage / framework, il existe des règles ou des styles communs sur lesquels les développeurs sont d'accord. Les ignorer dans les projets privés semble faux. Et pour votre raisonnement "Dans mon code, je ne retourne pas de chaînes modifiables": cela pourrait fonctionner pour vos propres chaînes, mais vous ne savez jamais sur les chaînes que vous recevez des frameworks.
Nikolai Ruhe
7
@Nikolai Je n'utilise tout simplement pas NSMutableString, sauf en tant que type de "générateur de chaîne" transitoire (dont je prends immédiatement une copie immuable). Je préférerais qu'ils soient des types discrets - mais je permettrai que le fait que la copie soit libre de faire une conservation si la chaîne d'origine n'est pas modifiable atténue la plupart de mes inquiétudes.
philsquared
120

La copie doit être utilisée pour NSString. Si c'est Mutable, alors il est copié. Si ce n'est pas le cas, il est simplement conservé. Exactement la sémantique souhaitée dans une application (laissez le type faire ce qu'il y a de mieux).

Frank Krueger
la source
1
Je préférerais toujours que les formes mutables et immuables soient discrètes, mais je ne m'étais pas rendu compte avant que cette copie ne puisse être conservée si la chaîne d'origine est immuable - ce qui est la plupart du temps là-bas. Merci.
philsquared
25
+1 pour avoir mentionné que la NSStringpropriété déclarée copysera de retaintoute façon (si elle est immuable, bien sûr). Un autre exemple auquel je peux penser est NSNumber.
matm
Quelle est la différence entre cette réponse et celle rejetée par @GBY?
Gary Lyn
67

Pour les chaînes en général, est-ce toujours une bonne idée d'utiliser l'attribut copy au lieu de conserver?

Oui - en général, utilisez toujours l'attribut de copie.

Cela est dû au fait que votre propriété NSString peut passer une instance NSString ou une instance NSMutableString , et donc nous ne pouvons pas vraiment déterminer si la valeur transmise est un objet immuable ou modifiable.

Une propriété "copiée" est-elle d'une quelconque manière moins efficace qu'une telle propriété "conservée"?

  • Si votre propriété passe une instance NSString , la réponse est " Non " - la copie n'est pas moins efficace que la conservation.
    (Ce n'est pas moins efficace car la NSString est suffisamment intelligente pour ne pas réellement effectuer une copie.)

  • Si votre propriété reçoit une instance NSMutableString, la réponse est " Oui " - la copie est moins efficace que la conservation.
    (Il est moins efficace car une allocation de mémoire et une copie réelles doivent se produire, mais c'est probablement une chose souhaitable.)

  • D'une manière générale, une propriété «copiée» a le potentiel d'être moins efficace - cependant, grâce à l'utilisation du NSCopyingprotocole, il est possible d'implémenter une classe qui est «tout aussi efficace» à copier qu'à conserver. Les instances NSString en sont un exemple.

Généralement (pas seulement pour NSString), quand dois-je utiliser "copier" au lieu de "conserver"?

Vous devez toujours l'utiliser copylorsque vous ne voulez pas que l'état interne de la propriété change sans avertissement. Même pour les objets immuables - les objets immuables correctement écrits géreront efficacement la copie (voir la section suivante concernant l'immuabilité et NSCopying).

Les retainobjets peuvent avoir des raisons de performances , mais ils entraînent une surcharge de maintenance - vous devez gérer la possibilité que l'état interne change en dehors de votre code. Comme on dit - optimisez en dernier.

Mais, j'ai écrit que ma classe était immuable - ne puis-je pas simplement la "conserver"?

Pas d'utilisation copy. Si votre classe est vraiment immuable, il est préférable d'implémenter le NSCopyingprotocole pour que votre classe se retourne lorsqu'elle copyest utilisée. Si tu fais ça:

  • Les autres utilisateurs de votre classe bénéficieront des avantages de performances lorsqu'ils utiliseront copy.
  • L' copyannotation rend votre propre code plus facile à gérer - l' copyannotation indique que vous n'avez vraiment pas besoin de vous soucier du changement d'état de cet objet ailleurs.
TJez
la source
39

J'essaie de suivre cette règle simple:

  • Est-ce que je veux conserver la valeur de l'objet au moment où je l'attribue à ma propriété? Utilisez copie .

  • Est-ce que je veux m'accrocher à l' objet et je me fiche de ce que ses valeurs internes sont ou seront à l'avenir? Utiliser fort (conserver).

Pour illustrer: Est-ce que je veux conserver le nom "Lisa Miller" ( copie ) ou je veux conserver la personne Lisa Miller ( forte )? Son nom pourrait changer plus tard en "Lisa Smith", mais elle sera toujours la même personne.

Johannes Fahrenkrug
la source
14

Grâce à cet exemple, copier et conserver peut être expliqué comme suit:

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

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

[someName setString:@"Debajit"];

si la propriété est de type copie,

une nouvelle copie sera créée pour la [Person name]chaîne qui contiendra le contenu de la someNamechaîne. Désormais, toute opération sur someNamechaîne n'aura aucun effet [Person name].

[Person name]et les someNamechaînes auront des adresses mémoire différentes.

Mais en cas de retenue,

les deux [Person name]conserveront la même adresse mémoire que pour une chaîne de nom, seul le nombre de chaînes de nom retenu sera incrémenté de 1.

Ainsi, tout changement dans une chaîne de nom sera reflété dans la [Person name]chaîne.

Divya Arora
la source
3

Assurer «copier» sur une déclaration de propriété va sûrement à l'encontre de l'utilisation d'un environnement orienté objet où les objets sur le tas sont passés par référence - l'un des avantages que vous obtenez ici est que, lors du changement d'un objet, toutes les références à cet objet voir les dernières modifications. De nombreuses langues fournissent des mots-clés «ref» ou similaires pour permettre aux types de valeur (c'est-à-dire les structures sur la pile) de bénéficier du même comportement. Personnellement, j'utiliserais la copie avec parcimonie, et si je pensais qu'une valeur de propriété devait être protégée des modifications apportées à l'objet à partir duquel elle était affectée, je pouvais appeler la méthode de copie de cet objet pendant l'affectation, par exemple:

p.name = [someName copy];

Bien sûr, lors de la conception de l'objet qui contient cette propriété, vous seul saurez si la conception bénéficie d'un modèle dans lequel les affectations prennent des copies - Cocoawithlove.com a ce qui suit à dire:

"Vous devez utiliser un accesseur de copie lorsque le paramètre setter peut être modifiable, mais vous ne pouvez pas faire changer l'état interne d'une propriété sans avertissement " - donc vous décidez si vous pouvez supporter la valeur à modifier de manière inattendue. Imaginez ce scénario:

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

Dans ce cas, sans utiliser de copie, notre objet contact prend automatiquement la nouvelle valeur; si nous l'utilisions, cependant, nous devrions nous assurer manuellement que les modifications ont été détectées et synchronisées. Dans ce cas, conserver la sémantique peut être souhaitable; dans un autre, une copie pourrait être plus appropriée.

Clarkeye
la source
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
la source
0

Vous devez utiliser la copie tout le temps pour déclarer la propriété NSString

@property (nonatomic, copy) NSString* name;

Vous devriez les lire pour plus d'informations sur le fait qu'elle renvoie une chaîne immuable (au cas où une chaîne mutable a été transmise) ou renvoie une chaîne conservée (dans le cas où une chaîne immuable a été transmise)

Référence du protocole NSCopying

Implémentez NSCopying en conservant l'original au lieu de créer une nouvelle copie lorsque la classe et son contenu sont immuables

Objets de valeur

Donc, pour notre version immuable, nous pouvons simplement faire ceci:

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

Étant donné que le nom est un (immuable) NSString, copier ou conserver ne fait aucune différence si vous en définissez un autre NSStringsur name. Dans un autre mot, la copie se comporte comme la conservation, augmentant ainsi le nombre de références. Je pense que c'est une optimisation automatique pour les classes immuables, car elles sont immuables et n'ont pas besoin d'être clonées. Mais quand a NSMutalbeString mstrest défini sur name, le contenu de mstrsera copié par souci d'exactitude.

GBY
la source
1
Vous confondez le type déclaré avec le type réel. Si vous utilisez une propriété "conserver" et attribuez un NSMutableString, ce NSMutableString sera conservé, mais peut toujours être modifié. Si vous utilisez "copier", une copie immuable sera créée lorsque vous affecterez une NSMutableString; à partir de ce moment, la "copie" de la propriété sera conservée, car la copie de la chaîne modifiable est elle-même immuable.
gnasher729
1
Il vous manque des faits importants ici, si vous utilisez un objet provenant d'une variable conservée, lorsque cette variable est modifiée, votre objet aussi, s'il provient d'une variable copiée, votre objet aura la valeur actuelle de la variable qui ne changera pas
Bryan P
-1

Si la chaîne est très volumineuse, la copie affectera les performances et deux copies de la chaîne volumineuse utiliseront plus de mémoire.

jack
la source