NSString-Eigenschaft: kopieren oder aufbewahren?


331

Angenommen, ich habe eine Klasse SomeClassmit einem stringEigenschaftsnamen:

@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end

Ich verstehe, dass dem Namen ein Name zugewiesen werden kann. NSMutableStringIn diesem Fall kann dies zu fehlerhaftem Verhalten führen.

  • Ist es für Zeichenfolgen im Allgemeinen immer eine gute Idee, das copyAttribut anstelle von zu verwenden retain?
  • Ist eine "kopierte" Eigenschaft in irgendeiner Weise weniger effizient als eine solche "beibehaltene" Eigenschaft?

6
Folgefrage: Sollte namein veröffentlicht werden deallocoder nicht?
Chetan

7
@chetan Ja sollte es!
Jon

Antworten:


440

Für Attribute, deren Typ eine unveränderliche Wertklasse ist, die dem NSCopyingProtokoll entspricht, sollten Sie dies fast immer copyin Ihrer @propertyDeklaration angeben . Das Spezifizieren retainist etwas, das Sie in einer solchen Situation fast nie wollen.

Hier ist, warum Sie das tun möchten:

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

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

[someName setString:@"Debajit"];

Der aktuelle Wert der Person.nameEigenschaft hängt davon ab, ob die Eigenschaft deklariert ist retainoder copy- es ist, @"Debajit"wenn die Eigenschaft markiert ist retain, aber @"Chris"wenn die Eigenschaft markiert ist copy.

Da Sie in fast allen Fällen verhindern möchten, dass die Attribute eines Objekts hinter seinem Rücken mutiert werden, sollten Sie die Eigenschaften markieren, die sie darstellen copy. (Und wenn Sie den Setter selbst schreiben, anstatt ihn zu verwenden @synthesize, sollten Sie daran denken, ihn tatsächlich zu verwenden, copyanstatt retainihn zu verwenden.)


61
Diese Antwort hat möglicherweise einige Verwirrung gestiftet (siehe robnapier.net/blog/implementing-nscopying-439#comment-1312 ). Sie haben absolut Recht mit NSString, aber ich glaube, Sie haben den Punkt etwas zu allgemein gehalten. Der Grund, warum NSString kopiert werden sollte, ist, dass es eine gemeinsame veränderbare Unterklasse (NSMutableString) hat. Für Klassen ohne veränderbare Unterklasse (insbesondere Klassen, die Sie selbst schreiben) ist es normalerweise besser, sie beizubehalten als zu kopieren, um Zeit- und Speicherverschwendung zu vermeiden.
Rob Napier

63
Ihre Argumentation ist falsch. Sie sollten nicht anhand der Zeit / des Speichers entscheiden, ob kopiert oder beibehalten werden soll, sondern anhand der gewünschten Semantik. Deshalb habe ich in meiner Antwort speziell den Begriff "unveränderliche Wertklasse" verwendet. Es geht auch nicht darum, ob eine Klasse veränderbare Unterklassen hat oder selbst veränderbar ist.
Chris Hanson

10
Es ist eine Schande, dass Obj-C die Unveränderlichkeit nicht nach Typ erzwingen kann. Dies ist dasselbe wie das Fehlen einer transitiven Konstante in C ++. Persönlich arbeite ich so, als wären Strings immer unveränderlich. Wenn ich jemals eine veränderbare Zeichenfolge verwenden muss, werde ich niemals eine unveränderliche Referenz ausgeben, wenn ich sie später mutieren darf. Ich würde alles andere als Code-Geruch betrachten. Infolgedessen verwende ich in meinem Code (an dem ich alleine arbeite) Retain für alle meine Zeichenfolgen. Wenn ich als Teil eines Teams arbeiten würde, könnte ich die Dinge anders sehen.
Philsquared

5
@Phil Nash: Ich würde es als Codegeruch betrachten, verschiedene Stile für Projekte zu verwenden, an denen Sie alleine arbeiten, und für Projekte, die Sie mit anderen teilen. In jeder Sprache / jedem Framework gibt es gemeinsame Regeln oder Stile, auf die sich Entwickler einigen. Sie in privaten Projekten zu ignorieren, scheint falsch. Und zu Ihrer Begründung "In meinem Code gebe ich keine veränderlichen Zeichenfolgen zurück": Das funktioniert möglicherweise für Ihre eigenen Zeichenfolgen, aber Sie wissen nie, welche Zeichenfolgen Sie von Frameworks erhalten.
Nikolai Ruhe

7
@Nikolai Ich benutze es nur nicht NSMutableString, außer als vorübergehender "String Builder" -Typ (von dem ich sofort eine unveränderliche Kopie nehme). Ich würde es vorziehen, wenn es sich um diskrete Typen handelt - aber ich werde zulassen, dass die Tatsache, dass die Kopie frei aufbewahrt werden kann, wenn die ursprüngliche Zeichenfolge nicht veränderbar ist, den größten Teil meiner Bedenken mindert.
Philsquared

120

Für NSString sollte eine Kopie verwendet werden. Wenn es veränderlich ist, wird es kopiert. Wenn dies nicht der Fall ist, wird es einfach beibehalten. Genau die Semantik, die Sie in einer App wünschen (lassen Sie den Typ das Beste tun).


1
Ich würde es immer noch vorziehen, wenn die veränderlichen und unveränderlichen Formen diskret wären, aber ich hatte vorher nicht bemerkt, dass diese Kopie möglicherweise nur beibehalten wird, wenn die ursprüngliche Zeichenfolge unveränderlich ist - was meistens der Fall ist. Vielen Dank.
Philsquared

25
+1 für die Erwähnung dieser NSStringEigenschaft, die als ohnehin deklariert copywird retain(wenn sie unveränderlich ist, natürlich). Ein anderes Beispiel, an das ich denken kann, ist NSNumber.
Matm

Was ist der Unterschied zwischen dieser Antwort und der von @GBY gewählten Antwort?
Gary Lyn

67

Ist es für Zeichenfolgen im Allgemeinen immer eine gute Idee, das Attribut copy zu verwenden, anstatt es beizubehalten?

Ja - im Allgemeinen verwenden Sie immer das Attribut copy.

Dies liegt daran, dass Ihrer NSString-Eigenschaft eine NSString-Instanz oder eine NSMutableString-Instanz übergeben werden kann. Daher können wir nicht wirklich feststellen, ob der übergebene Wert ein unveränderliches oder veränderliches Objekt ist.

Ist eine "kopierte" Eigenschaft in irgendeiner Weise weniger effizient als eine solche "beibehaltene" Eigenschaft?

  • Wenn Ihrer Eigenschaft eine NSString-Instanz übergeben wird , lautet die Antwort " Nein " - das Kopieren ist nicht weniger effizient als das Beibehalten.
    (Es ist nicht weniger effizient, da der NSString intelligent genug ist, um keine Kopie auszuführen.)

  • Wenn Ihrer Eigenschaft eine NSMutableString-Instanz übergeben wird, lautet die Antwort " Ja " - das Kopieren ist weniger effizient als das Beibehalten.
    (Es ist weniger effizient, da eine tatsächliche Speicherzuweisung und -kopie erfolgen muss, dies ist jedoch wahrscheinlich wünschenswert.)

  • Im Allgemeinen kann eine "kopierte" Eigenschaft weniger effizient sein. Durch die Verwendung des NSCopyingProtokolls ist es jedoch möglich, eine Klasse zu implementieren, deren Kopie "genauso effizient" ist wie deren Beibehaltung. Ein Beispiel hierfür sind NSString-Instanzen .

Im Allgemeinen (nicht nur für NSString), wann sollte ich "Kopieren" anstelle von "Beibehalten" verwenden?

Sie sollten immer verwenden, copywenn Sie nicht möchten, dass sich der interne Status der Eigenschaft ohne Vorwarnung ändert. Selbst für unveränderliche Objekte - ordnungsgemäß geschriebene unveränderliche Objekte verarbeiten das Kopieren effizient (siehe nächster Abschnitt zu Unveränderlichkeit und NSCopying).

Es kann Leistungsgründe für retainObjekte geben, die jedoch mit einem Wartungsaufwand verbunden sind. Sie müssen die Möglichkeit verwalten, dass sich der interne Status außerhalb Ihres Codes ändert. Wie sie sagen - zuletzt optimieren.

Aber ich habe meine Klasse als unveränderlich geschrieben - kann ich sie nicht einfach "behalten"?

Nicht verwenden copy. Wenn Ihre Klasse wirklich unveränderlich ist, empfiehlt es sich, das NSCopyingProtokoll so zu implementieren , dass Ihre Klasse bei copyVerwendung selbst zurückkehrt. Wenn du das tust:

  • Andere Benutzer Ihrer Klasse erhalten die Leistungsvorteile, wenn sie sie verwenden copy.
  • Die copyAnnotation macht Ihren eigenen Code wartbarer - die copyAnnotation zeigt an, dass Sie sich wirklich keine Sorgen machen müssen, dass dieses Objekt den Status an anderer Stelle ändert.

39

Ich versuche diese einfache Regel zu befolgen:

  • Möchte ich den Wert des Objekts zu dem Zeitpunkt beibehalten, zu dem ich es meiner Eigenschaft zuweise? Verwendung kopieren .

  • Möchte ich mich an dem Objekt festhalten und es ist mir egal, welche internen Werte aktuell sind oder in Zukunft sein werden? Verwenden Sie stark (behalten).

Zur Veranschaulichung: Möchte ich an dem Namen "Lisa Miller" ( Kopie ) festhalten oder an der Person Lisa Miller ( stark ) festhalten ? Ihr Name könnte sich später in "Lisa Smith" ändern, aber sie wird immer noch dieselbe Person sein.


14

Anhand dieses Beispiels kann das Kopieren und Aufbewahren wie folgt erklärt werden:

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

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

[someName setString:@"Debajit"];

Wenn die Eigenschaft vom Typ copy ist, dann

Für die [Person name]Zeichenfolge, die den Inhalt der Zeichenfolge enthält, wird eine neue Kopie erstellt someName. Jetzt hat jede Operation an einer someNameZeichenfolge keine Auswirkung mehr auf [Person name].

[Person name]und someNameZeichenfolgen haben unterschiedliche Speicheradressen.

Aber im Falle der Beibehaltung,

Beide enthalten [Person name]dieselbe Speicheradresse wie die Somename-Zeichenfolge. Nur die Anzahl der Beibehaltungen der Somename-Zeichenfolge wird um 1 erhöht.

Jede Änderung der Zeichenfolge eines Somennamens wird in der [Person name]Zeichenfolge wiedergegeben.


3

Das Kopieren einer Eigenschaftsdeklaration mit "Kopieren" steht im Widerspruch zur Verwendung einer objektorientierten Umgebung, in der Objekte auf dem Heap als Referenz übergeben werden. Einer der Vorteile, die Sie hier erhalten, besteht darin, dass beim Ändern eines Objekts alle Verweise auf dieses Objekt vorhanden sind siehe die neuesten Änderungen. Viele Sprachen bieten 'ref' oder ähnliche Schlüsselwörter an, damit Werttypen (dh Strukturen auf dem Stapel) vom gleichen Verhalten profitieren können. Persönlich würde ich das Kopieren sparsam verwenden, und wenn ich der Meinung wäre, dass ein Eigenschaftswert vor Änderungen an dem Objekt geschützt werden sollte, von dem er zugewiesen wurde, könnte ich die Kopiermethode dieses Objekts während der Zuweisung aufrufen, z.

p.name = [someName copy];

Wenn Sie das Objekt entwerfen, das diese Eigenschaft enthält, wissen natürlich nur Sie, ob das Design von einem Muster profitiert, bei dem Zuweisungen kopiert werden - Cocoawithlove.com hat Folgendes zu sagen:

"Sie sollten einen Kopier-Accessor verwenden, wenn der Setter-Parameter möglicherweise veränderbar ist, der interne Status einer Eigenschaft jedoch nicht ohne Vorwarnung geändert werden kann." Die Entscheidung, ob Sie den Wert für eine unerwartete Änderung aushalten können, liegt also ganz bei Ihnen. Stellen Sie sich dieses Szenario vor:

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

In diesem Fall nimmt unser Kontaktobjekt ohne Verwendung der Kopie automatisch den neuen Wert an. Wenn wir es jedoch verwenden würden, müssten wir manuell sicherstellen, dass Änderungen erkannt und synchronisiert werden. In diesem Fall kann es wünschenswert sein, die Semantik beizubehalten. In einem anderen Fall ist eine Kopie möglicherweise besser geeignet.


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

Sie sollten Gebrauch kopieren die ganze Zeit NSString Eigenschaft zu erklären

@property (nonatomic, copy) NSString* name;

Sie sollten diese lesen, um weitere Informationen darüber zu erhalten, ob eine unveränderliche Zeichenfolge zurückgegeben wird (falls eine veränderbare Zeichenfolge übergeben wurde) oder eine beibehaltene Zeichenfolge zurückgegeben wird (falls eine unveränderliche Zeichenfolge übergeben wurde).

NSCopying-Protokollreferenz

Implementieren Sie NSCopying, indem Sie das Original beibehalten, anstatt eine neue Kopie zu erstellen, wenn die Klasse und ihr Inhalt unveränderlich sind

Wertobjekte

Für unsere unveränderliche Version können wir also Folgendes tun:

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

-1

Da der Name ein (unveränderlicher) Name ist NSString, macht das Kopieren oder Behalten keinen Unterschied, wenn Sie einen anderen NSStringNamen festlegen . Mit anderen Worten, das Kopieren verhält sich wie das Beibehalten und erhöht die Referenzanzahl um eins. Ich denke, das ist eine automatische Optimierung für unveränderliche Klassen, da sie unveränderlich sind und nicht geklont werden müssen. Wenn jedoch a NSMutalbeString mstrauf name gesetzt ist, wird der Inhalt von mstraus Gründen der Richtigkeit kopiert.


1
Sie verwechseln den deklarierten Typ mit dem tatsächlichen Typ. Wenn Sie eine "beibehalten" -Eigenschaft verwenden und einen NSMutableString zuweisen, wird dieser NSMutableString beibehalten, kann jedoch weiterhin geändert werden. Wenn Sie "copy" verwenden, wird eine unveränderliche Kopie erstellt, wenn Sie einen NSMutableString zuweisen. Von da an bleibt "copy" auf der Eigenschaft nur erhalten, da die Kopie der veränderlichen Zeichenfolge selbst unveränderlich ist.
Gnasher729

1
Sie vermissen hier einige wichtige Fakten, wenn Sie ein Objekt verwenden, das aus einer beibehaltenen Variablen stammt, wenn diese Variable geändert wird, und Ihr Objekt, wenn es aus einer kopierten Variablen stammt, hat Ihr Objekt den aktuellen Wert der Variablen, die wird sich nicht ändern
Bryan P

-1

Wenn die Zeichenfolge sehr groß ist, wirkt sich das Kopieren auf die Leistung aus, und zwei Kopien der großen Zeichenfolge benötigen mehr Speicher.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.