Proprietà NSString: copia o conserva?


331

Diciamo che ho una classe chiamata SomeClasscon un stringnome di proprietà:

@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end

Comprendo che il nome potrebbe essere assegnato a NSMutableStringnel qual caso ciò potrebbe portare a comportamenti errati.

  • Per le stringhe in generale, è sempre una buona idea utilizzare l' copyattributo anziché retain?
  • Una proprietà "copiata" è in qualche modo meno efficiente di una proprietà "mantenuta"?

6
Domanda di follow-up: dovrebbe nameessere rilasciato dealloco no?
Chetan,

7
@chetan Sì, dovrebbe!
Jon

Risposte:


440

Per gli attributi il ​​cui tipo è una classe di valori immutabili conforme al NSCopyingprotocollo, è necessario specificare quasi sempre copynella propria @propertydichiarazione. Specificare retainè qualcosa che non vorrai quasi mai in una situazione del genere.

Ecco perché vuoi farlo:

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

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

[someName setString:@"Debajit"];

Il valore corrente della Person.nameproprietà sarà diverso a seconda che la proprietà sia dichiarata retaino copy- lo sarà @"Debajit"se la proprietà è contrassegnata retain, ma @"Chris"se la proprietà è contrassegnata copy.

Poiché in quasi tutti i casi si desidera impedire la mutazione degli attributi di un oggetto dietro la sua schiena, è necessario contrassegnare le proprietà che li rappresentano copy. (E se scrivi tu stesso il setter invece di usarlo @synthesize, dovresti ricordarti di usarlo copyinvece che retainin esso.)


61
Questa risposta potrebbe aver causato un po 'di confusione (vedi robnapier.net/blog/implementing-nscopying-439#comment-1312 ). Hai assolutamente ragione su NSString, ma credo che tu abbia sottolineato un po 'troppo il punto. Il motivo per cui NSString deve essere copiato è che ha una sottoclasse mutabile comune (NSMutableString). Per le classi che non hanno una sottoclasse mutabile (in particolare le classi che scrivi tu stesso), di solito è meglio conservarle anziché copiarle per evitare di perdere tempo e memoria.
Rob Napier,

63
Il tuo ragionamento non è corretto. Non dovresti decidere se copiare o conservare in base al tempo / alla memoria, dovresti determinarlo in base alla semantica desiderata. Ecco perché ho usato specificamente il termine "classe di valore immutabile" nella mia risposta. Inoltre, non si tratta di stabilire se una classe ha sottoclassi mutabili o se è mutabile.
Chris Hanson,

10
È un peccato che Obj-C non possa imporre l'immutabilità per tipo. Questo è lo stesso della mancanza di const transitiva di C ++. Personalmente lavoro come se le stringhe fossero sempre immutabili. Se mai avessi bisogno di usare una stringa mutabile non distribuirò mai un riferimento immutabile se potessi mutarlo in seguito. Considererei qualsiasi cosa diversa come un odore di codice. Di conseguenza - nel mio codice (su cui lavoro da solo) uso il mantenimento su tutte le mie stringhe. Se lavorassi come parte di una squadra, potrei guardare le cose in modo diverso.
philsquared

5
@Phil Nash: considererei un odore di codice usare stili diversi per progetti su cui lavori da solo e progetti che condividi con altri. In ogni lingua / quadro ci sono regole o stili comuni su cui gli sviluppatori concordano. Ignorarli nei progetti privati ​​sembra sbagliato. E per la tua logica "Nel mio codice, non sto restituendo stringhe mutabili": potrebbe funzionare per le tue stringhe, ma non sai mai delle stringhe che ricevi dai framework.
Nikolai Ruhe,

7
@Nikolai Semplicemente non lo uso NSMutableString, tranne che come un tipo "generatore di stringhe" transitorio (dal quale prendo immediatamente una copia immutabile). Preferirei che fossero tipi discreti, ma consentirò che il fatto che la copia sia libera di conservare se la stringa originale non è mutabile mitiga la maggior parte delle mie preoccupazioni.
philsquared

120

Copia dovrebbe essere usato per NSString. Se è mutabile, viene copiato. In caso contrario, viene semplicemente trattenuto. Esattamente la semantica che desideri in un'app (lascia che il tipo faccia il meglio).


1
Preferirei comunque che le forme mutabili e immutabili fossero discrete, ma non mi ero reso conto prima che quella copia potesse essere mantenuta solo se la stringa originale fosse immutabile, il che è per lo più lì. Grazie.
philsquared

25
+1 per menzionare quella NSStringproprietà dichiarata come copyotterrà retaincomunque (se è immutabile, ovviamente). Altro esempio che mi viene in mente è NSNumber.
matm

Qual è la differenza tra questa risposta e quella giù votata da @GBY?
Gary Lyn,

67

Per le stringhe in generale, è sempre una buona idea utilizzare l'attributo copia invece di conservare?

Sì - in generale usa sempre l'attributo copy.

Questo perché alla tua proprietà NSString può essere passata un'istanza NSString o un'istanza NSMutableString , e quindi non possiamo davvero determinare se il valore che viene passato è un oggetto immutabile o mutabile.

Una proprietà "copiata" è in qualche modo meno efficiente di una proprietà "mantenuta"?

  • Se la tua proprietà viene passata a un'istanza NSString , la risposta è " No " - la copia non è meno efficiente del mantenimento.
    (Non è meno efficiente perché NSString è abbastanza intelligente da non eseguire effettivamente una copia.)

  • Se alla tua proprietà viene passata un'istanza NSMutableString, la risposta è " " - la copia è meno efficiente della conservazione.
    (È meno efficiente perché devono verificarsi un'allocazione e una copia effettive della memoria, ma questa è probabilmente una cosa desiderabile.)

  • In generale, una proprietà "copiata" ha il potenziale per essere meno efficiente, tuttavia attraverso l'uso del NSCopyingprotocollo è possibile implementare una classe "altrettanto efficiente" da copiare come da conservare. Le istanze di NSString ne sono un esempio.

Generalmente (non solo per NSString), quando dovrei usare "copia" invece di "trattenere"?

Dovresti sempre usarlo copyquando non vuoi che lo stato interno della proprietà cambi senza preavviso. Anche per oggetti immutabili, gli oggetti immutabili scritti correttamente gestiranno la copia in modo efficiente (vedere la sezione successiva relativa all'immutabilità e NSCopying).

Potrebbero esserci motivi di prestazioni per gli retainoggetti, ma viene fornito con un overhead di manutenzione: è necessario gestire la possibilità che lo stato interno cambi al di fuori del codice. Come si suol dire, ottimizzare per ultimo.

Ma ho scritto che la mia classe è immutabile, non posso semplicemente "conservarla"?

No - usa copy. Se la tua classe è davvero immutabile, è consigliabile implementare il NSCopyingprotocollo per fare in modo che la tua classe si restituisca quando copyviene usata. Se lo fai:

  • Altri utenti della tua classe otterranno i vantaggi in termini di prestazioni quando lo usano copy.
  • L' copyannotazione rende il tuo codice più gestibile: l' copyannotazione indica che non devi preoccuparti che questo oggetto cambi stato altrove.

39

Provo a seguire questa semplice regola:

  • Voglio mantenere il valore dell'oggetto nel momento in cui lo sto assegnando alla mia proprietà? Usa copia .

  • Voglio aggrapparmi all'oggetto e non mi interessa quali sono i suoi valori interni attualmente o che saranno in futuro? Usa forte (mantieni).

Per illustrare: voglio mantenere il nome "Lisa Miller" ( copia ) o voglio mantenere la persona Lisa Miller ( forte )? Il suo nome potrebbe in seguito cambiare in "Lisa Smith", ma sarà sempre la stessa persona.


14

Attraverso questo esempio copia e conserva possono essere spiegati come:

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

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

[someName setString:@"Debajit"];

se la proprietà è di tipo copia,

verrà creata una nuova copia per la [Person name]stringa che conterrà il contenuto della someNamestringa. Ora qualsiasi operazione su someNamestringa non avrà alcun effetto su [Person name].

[Person name]e le someNamestringhe avranno indirizzi di memoria diversi.

Ma in caso di ritenzione,

entrambi [Person name]manterranno lo stesso indirizzo di memoria della stringa di somename, solo il conteggio della stringa di somename verrà incrementato di 1.

Quindi qualsiasi cambiamento nella stringa di somename si rifletterà nella [Person name]stringa.


3

Sicuramente mettere 'copia' su una dichiarazione di proprietà vola di fronte all'uso di un ambiente orientato agli oggetti in cui gli oggetti sull'heap vengono passati per riferimento - uno dei vantaggi che ottieni qui è che, quando si modifica un oggetto, tutti i riferimenti a quell'oggetto vedere le ultime modifiche. Molte lingue forniscono "ref" o parole chiave simili per consentire ai tipi di valore (ovvero strutture in pila) di beneficiare dello stesso comportamento. Personalmente, userei la copia con parsimonia, e se sentissi che un valore di proprietà dovrebbe essere protetto dalle modifiche apportate all'oggetto da cui è stato assegnato, potrei chiamare il metodo di copia di quell'oggetto durante l'assegnazione, ad esempio:

p.name = [someName copy];

Naturalmente, quando si progetta l'oggetto che contiene quella proprietà, solo tu saprai se il design beneficia di uno schema in cui le assegnazioni prendono copie - Cocoawithlove.com ha il seguente da dire:

"Dovresti usare un accessore di copia quando il parametro setter può essere mutabile ma non puoi avere lo stato interno di una proprietà che cambia senza preavviso " - quindi il giudizio sul fatto che puoi sopportare il valore da modificare inaspettatamente è tutto tuo. Immagina questo scenario:

//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 questo caso, senza usare la copia, il nostro oggetto contatto prende automaticamente il nuovo valore; se lo avessimo usato, tuttavia, avremmo dovuto assicurarci manualmente che le modifiche fossero rilevate e sincronizzate. In questo caso, mantenere la semantica potrebbe essere desiderabile; in un altro, la copia potrebbe essere più appropriata.


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

Utilizzare sempre copia per dichiarare la proprietà NSString

@property (nonatomic, copy) NSString* name;

Dovresti leggere queste informazioni per ulteriori informazioni sul fatto che restituisca una stringa immutabile (nel caso in cui sia stata passata una stringa mutabile) o restituisca una stringa mantenuta (nel caso in cui fosse passata una stringa immutabile)

Riferimento al protocollo NSCopying

Implementa NSCopying conservando l'originale anziché crearne una nuova quando la classe e il suo contenuto sono immutabili

Oggetti valore

Quindi, per la nostra versione immutabile, possiamo semplicemente fare questo:

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

-1

Poiché name è un (immutabile) NSString, copiare o conservare non fa alcuna differenza se ne imposti un altro NSString. In altre parole, la copia si comporta esattamente come conserva, aumentando il conteggio dei riferimenti di uno. Penso che sia un'ottimizzazione automatica per le classi immutabili, poiché sono immutabili e non hanno bisogno di essere clonate. Ma quando a NSMutalbeString mstrviene impostato il nome, il contenuto di mstrverrà copiato per motivi di correttezza.


1
Stai confondendo il tipo dichiarato con il tipo effettivo. Se si utilizza una proprietà "retain" e si assegna un NSMutableString, tale NSMutableString verrà mantenuto, ma potrà comunque essere modificato. Se si utilizza "copia", verrà creata una copia immutabile quando si assegna un NSMutableString; da allora in poi "copia" sulla proprietà manterrà solo, poiché la copia della stringa mutabile è essa stessa immutabile.
gnasher729,

1
Qui manchi alcuni fatti importanti, se usi un oggetto proveniente da una variabile mantenuta, quando tale variabile viene modificata, così fa il tuo oggetto, se proviene da una variabile copiata, il tuo oggetto avrà il valore corrente della variabile che non cambierà
Bryan P,

-1

Se la stringa è molto grande, la copia influirà sulle prestazioni e due copie della stringa grande utilizzeranno più memoria.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.