Come copiare un oggetto in Objective-C


112

Ho bisogno di copiare in profondità un oggetto personalizzato che ha oggetti propri. Ho letto in giro e sono un po 'confuso su come ereditare NSCopying e su come usare NSCopyObject.


1
Il grande TUTORIAL per comprendere copy, mutableCopy e copyWithZone
horkavlna

Risposte:


192

Come sempre con i tipi di riferimento, ci sono due nozioni di "copia". Sono sicuro che li conosci, ma per completezza.

  1. Una copia bit per bit. In questo, copiamo solo bit di memoria per bit: questo è ciò che fa NSCopyObject. Quasi sempre, non è quello che vuoi. Gli oggetti hanno uno stato interno, altri oggetti, ecc. E spesso si presuppone che siano gli unici a contenere riferimenti a tali dati. Le copie bit per bit infrangono questa ipotesi.
  2. Una copia logica e profonda. In questo, creiamo una copia dell'oggetto, ma senza farlo effettivamente a poco a poco - vogliamo un oggetto che si comporti allo stesso modo a tutti gli effetti, ma non (necessariamente) un clone identico alla memoria dell'originale - il manuale Objective C chiama tale oggetto "funzionalmente indipendente" dal suo originale. Poiché i meccanismi per creare queste copie "intelligenti" variano da classe a classe, chiediamo agli oggetti stessi di eseguirli. Questo è il protocollo NSCopying.

Tu vuoi il secondo. Se questo è uno dei tuoi oggetti, devi semplicemente adottare il protocollo NSCopying e implementare - (id) copyWithZone: (NSZone *) zone. Sei libero di fare quello che vuoi; anche se l'idea è che tu faccia una copia reale di te stesso e la restituisci. Chiami copyWithZone su tutti i tuoi campi, per fare una copia completa. Un semplice esempio è

@interface YourClass : NSObject <NSCopying> 
{
   SomeOtherObject *obj;
}

// In the implementation
-(id)copyWithZone:(NSZone *)zone
{
  // We'll ignore the zone for now
  YourClass *another = [[YourClass alloc] init];
  another.obj = [obj copyWithZone: zone];

  return another;
}

Ma stai rendendo il destinatario dell'oggetto copiato responsabile di liberarlo! Non dovresti autorelease, o mi sto perdendo qualcosa qui?
bobobobo

30
@bobobobo: No, la regola fondamentale della gestione della memoria di Objective-C è: prendi la proprietà di un oggetto se lo crei usando un metodo il cui nome inizia con "alloc" o "new" o contiene "copy". copyWithZone:soddisfa questo criterio, quindi deve restituire un oggetto con un conteggio di conservazione pari a +1.
Steve Madsen

1
@Adam C'è un motivo per usarlo allocinvece che da allocWithZone:quando la zona è stata passata?
Richard

3
Ebbene, le zone sono effettivamente inutilizzate nei moderni runtime basati su OS X (cioè penso che non siano mai state usate letteralmente). Ma sì, potresti chiamare allocWithZone.
Adam Wright


25

La documentazione di Apple dice

Una versione sottoclasse del metodo copyWithZone: dovrebbe inviare prima il messaggio a super, per incorporarne l'implementazione, a meno che la sottoclasse non discenda direttamente da NSObject.

da aggiungere alla risposta esistente

@interface YourClass : NSObject <NSCopying> 
{
   SomeOtherObject *obj;
}

// In the implementation
-(id)copyWithZone:(NSZone *)zone
{
  YourClass *another = [super copyWithZone:zone];
  another.obj = [obj copyWithZone: zone];

  return another;
}

2
Dato che YourClass discende direttamente da NSObject, non credo sia necessario qui
Mike il

2
Buon punto, ma è una regola generale, nel caso sia una lunga gerarchia di classi.
Saqib Saud

8
Ho ottenuto un errore: No visible @interface for 'NSObject' declares the selector 'copyWithZone:'. Immagino che questo sia richiesto solo quando ereditiamo da un'altra classe personalizzata che implementacopyWithZone
Sam il

1
another.obj = [[obj copyWithZone: zone] autorelease]; per tutte le sottoclassi di NSObject. E per i tipi di dati primitivi devi semplicemente assegnarli -> another.someBOOL = self.someBOOL;
hariszaman

@Sam "NSObject non supporta di per sé il protocollo NSCopying. Le sottoclassi devono supportare il protocollo e implementare il metodo copyWithZone:. Una versione sottoclasse del metodo copyWithZone: deve inviare il messaggio prima a super, per incorporarne l'implementazione, a meno che la sottoclasse non discenda direttamente da NSObject. " developer.apple.com/documentation/objectivec/nsobject/…
s4mt6

21

Non conosco la differenza tra quel codice e il mio, ma ho problemi con quella soluzione, quindi ho letto un po 'di più e ho scoperto che dobbiamo impostare l'oggetto prima di restituirlo. Intendo qualcosa come:

#import <Foundation/Foundation.h>

@interface YourObject : NSObject <NSCopying>

@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *line;
@property (strong, nonatomic) NSMutableString *tags;
@property (strong, nonatomic) NSString *htmlSource;
@property (strong, nonatomic) NSMutableString *obj;

-(id) copyWithZone: (NSZone *) zone;

@end


@implementation YourObject


-(id) copyWithZone: (NSZone *) zone
{
    YourObject *copy = [[YourObject allocWithZone: zone] init];

    [copy setNombre: self.name];
    [copy setLinea: self.line];
    [copy setTags: self.tags];
    [copy setHtmlSource: self.htmlSource];

    return copy;
}

Ho aggiunto questa risposta perché ho molti problemi con questo problema e non ho idea del motivo per cui sta accadendo. Non conosco la differenza, ma per me funziona e forse può essere utile anche per gli altri :)


3
another.obj = [obj copyWithZone: zone];

Penso che questa riga provochi perdite di memoria, perché si accede a una objproprietà che è (presumo) dichiarata come retain. Quindi, il conteggio della conservazione sarà aumentato dalla proprietà e copyWithZone.

Credo che dovrebbe essere:

another.obj = [[obj copyWithZone: zone] autorelease];

o:

SomeOtherObject *temp = [obj copyWithZone: zone];
another.obj = temp;
[temp release]; 

No, i metodi alloc, copy, mutableCopy, new dovrebbero restituire oggetti non rilasciati automaticamente.
kovpas

@kovpas, sei sicuro di capirmi, giusto? Non sto parlando dell'oggetto restituito, sto parlando dei suoi campi dati.
Szuwar_Jr

sì, colpa mia, scusa. potresti modificare la tua risposta in qualche modo in modo da poter rimuovere il segno meno? :))
kovpas

0

C'è anche l'uso dell'operatore -> per la copia. Per esempio:

-(id)copyWithZone:(NSZone*)zone
{
    MYClass* copy = [MYClass new];
    copy->_property1 = self->_property1;
    ...
    copy->_propertyN = self->_propertyN;
    return copy;
}

Il ragionamento qui è che l'oggetto copiato risultante dovrebbe riflettere lo stato dell'oggetto originale. Il "." operatore potrebbe introdurre effetti collaterali in quanto questo chiama getter che a loro volta possono contenere logica.

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.