So kopieren Sie ein Objekt in Objective-C

112

Ich muss ein benutzerdefiniertes Objekt mit eigenen Objekten tief kopieren. Ich habe herumgelesen und bin etwas verwirrt darüber, wie man NSCopying erbt und wie man NSCopyObject verwendet.

ben
quelle
1
Das große TUTORIAL zum Verstehen von copy, mutableCopy und copyWithZone
horkavlna

Antworten:

192

Wie immer bei Referenztypen gibt es zwei Begriffe von "Kopie". Ich bin sicher, Sie kennen sie, aber der Vollständigkeit halber.

  1. Eine bitweise Kopie. In diesem Fall kopieren wir den Speicher nur Bit für Bit - genau das macht NSCopyObject. Fast immer ist es nicht das, was Sie wollen. Objekte haben einen internen Status, andere Objekte usw. und gehen häufig davon aus, dass sie die einzigen sind, die Verweise auf diese Daten enthalten. Bitweise Kopien brechen diese Annahme.
  2. Eine tiefe, logische Kopie. Dabei erstellen wir eine Kopie des Objekts, ohne dies jedoch Stück für Stück zu tun. Wir möchten ein Objekt, das sich in jeder Hinsicht gleich verhält, aber nicht (notwendigerweise) ein speicheridentischer Klon des Originals ist. Das Objective C-Handbuch nennt ein solches Objekt "funktional unabhängig" von seinem Original. Da die Mechanismen zum Erstellen dieser "intelligenten" Kopien von Klasse zu Klasse unterschiedlich sind, bitten wir die Objekte selbst, sie auszuführen. Dies ist das NSCopying-Protokoll.

Du willst Letzteres. Wenn dies eines Ihrer eigenen Objekte ist, müssen Sie einfach das Protokoll NSCopying übernehmen und implementieren - (id) copyWithZone: (NSZone *) zone. Sie können tun, was Sie wollen. Die Idee ist jedoch, dass Sie eine echte Kopie von sich selbst erstellen und diese zurückgeben. Sie rufen copyWithZone für alle Ihre Felder auf, um eine tiefe Kopie zu erstellen. Ein einfaches Beispiel ist

@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;
}
Adam Wright
quelle
Aber Sie machen den Empfänger des kopierten Objekts dafür verantwortlich, es freizugeben! Solltest du autoreleasees nicht, oder vermisse ich hier etwas?
Bobobobo
30
@bobobobo: Nein, die Grundregel der Objective-C-Speicherverwaltung lautet: Sie übernehmen das Eigentum an einem Objekt, wenn Sie es mit einer Methode erstellen, deren Name mit "alloc" oder "new" beginnt oder "copy" enthält. copyWithZone:erfüllt diese Kriterien, daher muss ein Objekt mit einer Anzahl von +1 zurückgegeben werden.
Steve Madsen
1
@Adam Gibt es einen Grund zu verwenden, allocanstatt allocWithZone:seit die Zone übergeben wurde?
Richard
3
Nun, Zonen werden in modernen OS X-basierten Laufzeiten praktisch nicht verwendet (dh ich denke, sie werden buchstäblich nie verwendet). Aber ja, du könntest anrufen allocWithZone.
Adam Wright
25

Apple-Dokumentation sagt

Eine Unterklassenversion der copyWithZone: -Methode sollte die Nachricht zuerst an super senden, um ihre Implementierung einzubeziehen, es sei denn, die Unterklasse stammt direkt von NSObject ab.

zur vorhandenen Antwort hinzufügen

@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;
}
Saqib Saud
quelle
2
Da YourClass direkt von NSObject abstammt, denke ich nicht, dass es hier notwendig ist
Mike
2
Guter Punkt, aber es ist eine allgemeine Regel, falls es eine lange Klassenhierarchie ist.
Saqib Saud
8
Ich habe einen Fehler bekommen : No visible @interface for 'NSObject' declares the selector 'copyWithZone:'. Ich denke, dies ist nur erforderlich, wenn wir von einer anderen benutzerdefinierten Klasse erben, die implementiertcopyWithZone
Sam
1
another.obj = [[obj copyWithZone: zone] autorelease]; für alle Unterklassen von NSObject. Und für primitive Datentypen weisen Sie sie einfach zu -> another.someBOOL = self.someBOOL;
Hariszaman
@Sam "NSObject selbst unterstützt das NSCopying-Protokoll nicht. Unterklassen müssen das Protokoll unterstützen und die copyWithZone: -Methode implementieren. Eine Unterklassenversion der copyWithZone: -Methode sollte die Nachricht zuerst an super senden, um ihre Implementierung einzubeziehen, es sei denn, die Unterklasse stammt direkt ab von NSObject. " developer.apple.com/documentation/objectivec/nsobject/…
s4mt6
21

Ich kenne den Unterschied zwischen diesem und meinem Code nicht, aber ich habe Probleme mit dieser Lösung. Deshalb habe ich ein bisschen mehr gelesen und festgestellt, dass wir das Objekt festlegen müssen, bevor wir es zurückgeben. Ich meine so etwas wie:

#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;
}

Ich habe diese Antwort hinzugefügt, weil ich viele Probleme mit diesem Problem habe und keine Ahnung habe, warum es passiert. Ich kenne den Unterschied nicht, aber es funktioniert für mich und vielleicht kann es auch für andere nützlich sein :)

Felipe Quirós
quelle
3
another.obj = [obj copyWithZone: zone];

Ich denke, dass diese Zeile einen Speicherverlust verursacht, weil Sie auf die objEigenschaft through zugreifen, die (ich nehme an) als deklariert ist retain. Die Anzahl der Einbehaltungen wird also durch das Eigentum und erhöht copyWithZone.

Ich glaube es sollte sein:

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

oder:

SomeOtherObject *temp = [obj copyWithZone: zone];
another.obj = temp;
[temp release]; 
Szuwar_Jr
quelle
Nein, Methoden zuweisen, kopieren, mutableCopy, new sollte nicht autorisierte Objekte zurückgeben.
Kovpas
@kovpas, bist du sicher, dass du mich richtig verstehst? Ich spreche nicht über zurückgegebenes Objekt, ich spreche über seine Datenfelder.
Szuwar_Jr
Ja, mein schlechtes, sorry. Könnten Sie bitte Ihre Antwort irgendwie bearbeiten, damit ich Minus entfernen kann? :))
Kovpas
0

Es gibt auch die Verwendung des Operators -> zum Kopieren. Beispielsweise:

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

Die Argumentation hier ist, dass das resultierende kopierte Objekt den Zustand des ursprünglichen Objekts widerspiegeln sollte. Das "." Der Bediener könnte Nebenwirkungen einführen, da dieser Getter aufruft, die wiederum Logik enthalten können.

Alex Nolasco
quelle